Fixed crash on sort on name, removed dependency on Picasso, require Android 5.1

This commit is contained in:
M66B 2017-11-05 10:54:49 +01:00
parent d485ffa50e
commit eb7334814c
7 changed files with 174 additions and 100 deletions

4
FAQ.md
View File

@ -48,7 +48,7 @@ However, NetGuard supports a [SOCKS5 proxy](https://en.wikipedia.org/wiki/SOCKS)
<a name="FAQ3"></a>
**(3) Can I use NetGuard on any Android version?**
No, the minimum required Android version is 5.0 (<a href= "https://developer.android.com/about/versions/android-5.0.html">LOLLIPOP</a>)
No, the minimum required Android version is 5.1 (<a href= "https://developer.android.com/about/versions/android-5.1.html">LOLLIPOP</a>)
<a name="FAQ4"></a>
**(4) Will NetGuard use extra battery power?**
@ -160,7 +160,7 @@ and is incorrectly attributed to NetGuard instead to the Google Play™ store ap
<a name="FAQ18"></a>
**(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.
<a name="FAQ19"></a>
**(19) Why does application XYZ still have internet access?**

View File

@ -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

View File

@ -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'

View File

@ -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);
}
}
});
}
}

View File

@ -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<AdapterRule.ViewHolder> implements Filterable {
@ -96,6 +97,8 @@ public class AdapterRule extends RecyclerView.Adapter<AdapterRule.ViewHolder> im
private List<Rule> listAll = new ArrayList<>();
private List<Rule> 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<AdapterRule.ViewHolder> 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<AdapterRule.ViewHolder> 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<AdapterRule.ViewHolder> 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<AdapterRule.ViewHolder> 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);
}
});
}
}
}
}

View File

@ -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<Rule>() {
@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<Rule>() {
@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<Rule>() {
@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<Rule>() {
@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;
}

View File

@ -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;