From eb7334814c2955ab78c7cefbadf09329319de98b Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 5 Nov 2017 10:54:49 +0100 Subject: [PATCH] Fixed crash on sort on name, removed dependency on Picasso, require Android 5.1 --- FAQ.md | 4 +- README.md | 5 +- app/build.gradle | 7 +- .../java/eu/faircode/netguard/AdapterLog.java | 70 ++++++------ .../eu/faircode/netguard/AdapterRule.java | 100 ++++++++++++------ .../main/java/eu/faircode/netguard/Rule.java | 56 +++++----- .../main/java/eu/faircode/netguard/Util.java | 32 ++++++ 7 files changed, 174 insertions(+), 100 deletions(-) diff --git a/FAQ.md b/FAQ.md index 469b5093..8885ca8a 100644 --- a/FAQ.md +++ b/FAQ.md @@ -48,7 +48,7 @@ However, NetGuard supports a [SOCKS5 proxy](https://en.wikipedia.org/wiki/SOCKS) **(3) Can I use NetGuard on any Android version?** -No, the minimum required Android version is 5.0 (LOLLIPOP) +No, the minimum required Android version is 5.1 (LOLLIPOP) **(4) Will NetGuard use extra battery power?** @@ -160,7 +160,7 @@ and is incorrectly attributed to NetGuard instead to the Google Play™ store ap **(18) Why can't I find NetGuard in the Google Play™ store app?** -NetGuard requires at least Android 5.0, so it is not available in the Google Play™ store app on devices running prior Android versions. +NetGuard requires at least Android 5.1, so it is not available in the Google Play™ store app on devices running prior Android versions. **(19) Why does application XYZ still have internet access?** diff --git a/README.md b/README.md index 5abbbc88..f8faf7cf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Features: * No calling home * No tracking or analytics * Actively developed and supported -* Android 5.0 and later supported +* Android 5.1 and later supported * IPv4/IPv6 TCP/UDP supported * Tethering supported * Multiple device users supported @@ -44,7 +44,7 @@ There is no other no-root firewall offering all these features. Requirements: -* Android 5.0 or later +* Android 5.1 or later * A [compatible device](#compatibility) Downloads: @@ -314,7 +314,6 @@ Attribution NetGuard uses: -* [Picasso](http://square.github.io/picasso/) * [Android Support Library](https://developer.android.com/tools/support-library/index.html) License diff --git a/app/build.gradle b/app/build.gradle index 9af7aa40..de722c3c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,10 +6,10 @@ android { defaultConfig { applicationId = "eu.faircode.netguard" - versionName = "2.149" + versionName = "2.150" minSdkVersion 22 targetSdkVersion 27 - versionCode = 2017110501 + versionCode = 2017110502 archivesBaseName = "NetGuard-v$versionName" externalNativeBuild { @@ -58,9 +58,6 @@ dependencies { // https://firebase.google.com/docs/android/setup implementation 'com.google.firebase:firebase-core:11.4.+' implementation 'com.google.firebase:firebase-ads:11.4.+' - - // https://mvnrepository.com/artifact/com.squareup.picasso/picasso - implementation 'com.squareup.picasso:picasso:2.5.+' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/eu/faircode/netguard/AdapterLog.java b/app/src/main/java/eu/faircode/netguard/AdapterLog.java index def3b843..59c465cd 100644 --- a/app/src/main/java/eu/faircode/netguard/AdapterLog.java +++ b/app/src/main/java/eu/faircode/netguard/AdapterLog.java @@ -23,12 +23,11 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Handler; @@ -45,13 +44,12 @@ import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; - import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.List; -import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class AdapterLog extends CursorAdapter { private static String TAG = "NetGuard.Log"; @@ -80,6 +78,8 @@ public class AdapterLog extends CursorAdapter { private InetAddress vpn4 = null; private InetAddress vpn6 = null; + private ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + public AdapterLog(Context context, Cursor cursor, boolean resolve, boolean organization) { super(context, cursor, 0); this.resolve = resolve; @@ -222,34 +222,44 @@ public class AdapterLog extends CursorAdapter { if (info == null) ivIcon.setImageDrawable(null); else { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - Icon icon; - if (info.icon == 0) - icon = Icon.createWithResource(context, android.R.drawable.sym_def_app_icon); - else - icon = Icon.createWithResource(info.packageName, info.icon); - try { - icon.loadDrawableAsync(context, new Icon.OnDrawableLoadedListener() { - @Override - public void onDrawableLoaded(Drawable drawable) { + if (info.icon <= 0) + ivIcon.setImageResource(android.R.drawable.sym_def_app_icon); + else { + ivIcon.setHasTransientState(true); + final ApplicationInfo finalInfo = info; + executor.submit(new Runnable() { + @Override + public void run() { + try { + Resources res = context.getPackageManager().getResourcesForApplication(finalInfo.packageName); + Drawable drawable = res.getDrawable(finalInfo.icon, null); + + final Drawable scaledDrawable; if (drawable instanceof BitmapDrawable) { - Bitmap original = ((BitmapDrawable) drawable).getBitmap(); - Bitmap scaled = Bitmap.createScaledBitmap(original, iconSize, iconSize, false); - ivIcon.setImageDrawable(new BitmapDrawable(context.getResources(), scaled)); + Bitmap scaled = Util.decodeSampledBitmapFromResource(res, finalInfo.icon, iconSize, iconSize); + scaledDrawable = new BitmapDrawable(context.getResources(), scaled); } else - ivIcon.setImageDrawable(drawable); + scaledDrawable = drawable; + + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + ivIcon.setImageDrawable(scaledDrawable); + ivIcon.setHasTransientState(false); + } + }); + } catch (Throwable ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + ivIcon.setImageDrawable(null); + ivIcon.setHasTransientState(false); + } + }); } - }, new Handler(context.getMainLooper())); - } catch (RejectedExecutionException ex) { - Log.w(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); - } - } else { - if (info.icon == 0) - Picasso.with(context).load(android.R.drawable.sym_def_app_icon).into(ivIcon); - else { - Uri uri = Uri.parse("android.resource://" + info.packageName + "/" + info.icon); - Picasso.with(context).load(uri).resize(iconSize, iconSize).into(ivIcon); - } + } + }); } } diff --git a/app/src/main/java/eu/faircode/netguard/AdapterRule.java b/app/src/main/java/eu/faircode/netguard/AdapterRule.java index 2d60726a..5d4235d7 100644 --- a/app/src/main/java/eu/faircode/netguard/AdapterRule.java +++ b/app/src/main/java/eu/faircode/netguard/AdapterRule.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.content.res.TypedArray; import android.database.Cursor; import android.graphics.Bitmap; @@ -71,11 +72,11 @@ import android.widget.PopupMenu; import android.widget.RelativeLayout; import android.widget.TextView; -import com.squareup.picasso.Picasso; - import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; public class AdapterRule extends RecyclerView.Adapter implements Filterable { @@ -96,6 +97,8 @@ public class AdapterRule extends RecyclerView.Adapter im private List listAll = new ArrayList<>(); private List listFiltered = new ArrayList<>(); + private ExecutorService executor = Executors.newCachedThreadPool(); + public static class ViewHolder extends RecyclerView.ViewHolder { public View view; @@ -154,6 +157,8 @@ public class AdapterRule extends RecyclerView.Adapter im public ImageButton btnClearAccess; public CheckBox cbNotify; + public IconLoader iconLoader = null; + public ViewHolder(View itemView) { super(itemView); view = itemView; @@ -339,38 +344,11 @@ public class AdapterRule extends RecyclerView.Adapter im holder.ivExpander.setImageLevel(rule.expanded ? 1 : 0); // Show application icon - if (rule.info.applicationInfo == null) - holder.ivIcon.setImageDrawable(null); + if (rule.info.applicationInfo.icon <= 0) + holder.ivIcon.setImageResource(android.R.drawable.sym_def_app_icon); else { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - final Icon icon; - if (rule.info.applicationInfo.icon == 0) - icon = Icon.createWithResource(context, android.R.drawable.sym_def_app_icon); - else - icon = Icon.createWithResource(rule.info.packageName, rule.info.applicationInfo.icon); - try { - icon.loadDrawableAsync(context, new Icon.OnDrawableLoadedListener() { - @Override - public void onDrawableLoaded(Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - Bitmap original = ((BitmapDrawable) drawable).getBitmap(); - Bitmap scaled = Bitmap.createScaledBitmap(original, iconSize, iconSize, false); - holder.ivIcon.setImageDrawable(new BitmapDrawable(context.getResources(), scaled)); - } else - holder.ivIcon.setImageDrawable(drawable); - } - }, new Handler(context.getMainLooper())); - } catch (RejectedExecutionException ex) { - Log.w(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); - } - } else { - if (rule.info.applicationInfo.icon == 0) - Picasso.with(context).load(android.R.drawable.sym_def_app_icon).into(holder.ivIcon); - else { - Uri uri = Uri.parse("android.resource://" + rule.info.packageName + "/" + rule.info.applicationInfo.icon); - Picasso.with(context).load(uri).resize(iconSize, iconSize).into(holder.ivIcon); - } - } + holder.iconLoader = new IconLoader(holder, rule); + executor.submit(holder.iconLoader); } // Show application label @@ -857,6 +835,9 @@ public class AdapterRule extends RecyclerView.Adapter im public void onViewRecycled(ViewHolder holder) { super.onViewRecycled(holder); + if (holder.iconLoader != null) + holder.iconLoader.cancel(); + CursorAdapter adapter = (CursorAdapter) holder.lvAccess.getAdapter(); if (adapter != null) { Log.i(TAG, "Closing access cursor"); @@ -1017,4 +998,57 @@ public class AdapterRule extends RecyclerView.Adapter im public int getItemCount() { return listFiltered.size(); } + + private class IconLoader implements Runnable { + private ViewHolder holder; + private Rule rule; + private boolean cancelled = false; + + public IconLoader(ViewHolder holder, Rule rule) { + this.holder = holder; + this.rule = rule; + holder.ivIcon.setHasTransientState(true); + } + + public void cancel() { + if (!cancelled) + Log.i(TAG, "Cancelling icon loader"); + cancelled = true; + } + + @Override + public void run() { + try { + if (cancelled) + throw new InterruptedException(); + + Resources res = context.getPackageManager().getResourcesForApplication(rule.info.packageName); + Drawable drawable = res.getDrawable(rule.info.applicationInfo.icon, null); + + final Drawable scaledDrawable; + if (drawable instanceof BitmapDrawable) { + Bitmap scaled = Util.decodeSampledBitmapFromResource(res, rule.info.applicationInfo.icon, iconSize, iconSize); + scaledDrawable = new BitmapDrawable(context.getResources(), scaled); + } else + scaledDrawable = drawable; + + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + holder.ivIcon.setImageDrawable(scaledDrawable); + holder.ivIcon.setHasTransientState(false); + } + }); + } catch (Throwable ex) { + Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + holder.ivIcon.setImageDrawable(null); + holder.ivIcon.setHasTransientState(false); + } + }); + } + } + } } diff --git a/app/src/main/java/eu/faircode/netguard/Rule.java b/app/src/main/java/eu/faircode/netguard/Rule.java index f873c6b1..0f38bbd0 100644 --- a/app/src/main/java/eu/faircode/netguard/Rule.java +++ b/app/src/main/java/eu/faircode/netguard/Rule.java @@ -419,36 +419,38 @@ public class Rule { Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex)); } - final Collator collator = Collator.getInstance(Locale.getDefault()); - collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc - // Sort rule list - String sort = prefs.getString("sort", "name"); - if ("uid".equals(sort)) - Collections.sort(listRules, new Comparator() { - @Override - public int compare(Rule rule, Rule other) { - if (rule.info.applicationInfo.uid < other.info.applicationInfo.uid) - return -1; - else if (rule.info.applicationInfo.uid > other.info.applicationInfo.uid) - return 1; - else { - int i = collator.compare(rule.name, other.name); - return (i == 0 ? rule.info.packageName.compareTo(other.info.packageName) : i); + if (!service) { + final Collator collator = Collator.getInstance(Locale.getDefault()); + collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc + + String sort = prefs.getString("sort", "name"); + if ("uid".equals(sort)) + Collections.sort(listRules, new Comparator() { + @Override + public int compare(Rule rule, Rule other) { + if (rule.info.applicationInfo.uid < other.info.applicationInfo.uid) + return -1; + else if (rule.info.applicationInfo.uid > other.info.applicationInfo.uid) + return 1; + else { + int i = collator.compare(rule.name, other.name); + return (i == 0 ? rule.info.packageName.compareTo(other.info.packageName) : i); + } } - } - }); - else - Collections.sort(listRules, new Comparator() { - @Override - public int compare(Rule rule, Rule other) { - if (all || rule.changed == other.changed) { - int i = collator.compare(rule.name, other.name); - return (i == 0 ? rule.info.packageName.compareTo(other.info.packageName) : i); + }); + else + Collections.sort(listRules, new Comparator() { + @Override + public int compare(Rule rule, Rule other) { + if (all || rule.changed == other.changed) { + int i = collator.compare(rule.name, other.name); + return (i == 0 ? rule.info.packageName.compareTo(other.info.packageName) : i); + } + return (rule.changed ? -1 : 1); } - return (rule.changed ? -1 : 1); - } - }); + }); + } return listRules; } diff --git a/app/src/main/java/eu/faircode/netguard/Util.java b/app/src/main/java/eu/faircode/netguard/Util.java index ffd00cc3..fba46882 100644 --- a/app/src/main/java/eu/faircode/netguard/Util.java +++ b/app/src/main/java/eu/faircode/netguard/Util.java @@ -31,7 +31,10 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; @@ -476,6 +479,35 @@ public class Util { return Math.round(dips * context.getResources().getDisplayMetrics().density + 0.5f); } + private static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + int height = options.outHeight; + int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + int halfHeight = height / 2; + int halfWidth = width / 2; + + while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) + inSampleSize *= 2; + } + + return inSampleSize; + } + + public static Bitmap decodeSampledBitmapFromResource( + Resources resources, int resourceId, int reqWidth, int reqHeight) { + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(resources, resourceId, options); + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + options.inJustDecodeBounds = false; + + return BitmapFactory.decodeResource(resources, resourceId, options); + } + public static String getProtocolName(int protocol, int version, boolean brief) { // https://en.wikipedia.org/wiki/List_of_IP_protocol_numbers String p = null;