1
0
Fork 0
mirror of https://github.com/M66B/NetGuard.git synced 2025-03-15 16:29:42 +00:00

Proper lifetime management, added menu, layout improvements

This commit is contained in:
M66B 2015-10-25 10:29:49 +01:00
parent 2f3e8f4e3f
commit 310872f432
15 changed files with 255 additions and 121 deletions

2
.idea/vcs.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -11,7 +11,9 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ActivityMain">
<activity
android:name=".ActivityMain"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -1,5 +1,6 @@
package eu.faircode.netguard;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.VpnService;
@ -10,25 +11,34 @@ import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;
import java.util.List;
public class ActivityMain extends AppCompatActivity {
public class ActivityMain extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "NetGuard.Main";
private static final int REQUEST_VPN = 1;
private boolean running = false;
private RuleAdapter adapter = null;
private static final int REQUEST_VPN = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "Create");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
running = true;
// Action bar
View view = getLayoutInflater().inflate(R.layout.actionbar, null);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setCustomView(view);
@ -39,8 +49,6 @@ public class ActivityMain extends AppCompatActivity {
Switch swEnabled = (Switch) view.findViewById(R.id.swEnabled);
swEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("enabled", isChecked).apply();
if (isChecked) {
Log.i(TAG, "On");
Intent intent = VpnService.prepare(ActivityMain.this);
@ -53,15 +61,18 @@ public class ActivityMain extends AppCompatActivity {
}
} else {
Log.i(TAG, "Off");
prefs.edit().putBoolean("enabled", false).apply();
Intent intent = new Intent(ActivityMain.this, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_START, false);
Log.i(TAG, "Stop service=" + intent);
intent.putExtra(BlackHoleService.EXTRA_COMMAND, BlackHoleService.Command.stop);
startService(intent);
}
}
});
swEnabled.setChecked(prefs.getBoolean("enabled", false));
// Listen for external enabled changes
prefs.registerOnSharedPreferenceChangeListener(this);
// Package list
final RecyclerView rvApplication = (RecyclerView) findViewById(R.id.rvApplication);
rvApplication.setHasFixedSize(true);
@ -83,19 +94,70 @@ public class ActivityMain extends AppCompatActivity {
}.execute();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String name) {
Log.i(TAG, "Changed pref=" + name);
if ("enabled".equals(name)) {
boolean enabled = prefs.getBoolean(name, false);
Switch swEnabled = (Switch) getSupportActionBar().getCustomView().findViewById(R.id.swEnabled);
if (swEnabled.isChecked() != enabled)
swEnabled.setChecked(enabled);
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Destroy");
running = false;
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.menu_vpn_settings:
Intent intent = new Intent("android.net.vpn.SETTINGS");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (intent.resolveActivity(getPackageManager()) != null)
startActivity(intent);
else
Log.w(TAG, intent + " not available");
return true;
case R.id.menu_about:
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.about, null);
TextView tvVersion = (TextView) view.findViewById(R.id.tvVersion);
tvVersion.setText(Util.getSelfVersionName(this));
AlertDialog dialog = new AlertDialog.Builder(this)
.setView(view)
.setCancelable(true).create();
dialog.show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_VPN) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", resultCode == RESULT_OK).apply();
if (resultCode == RESULT_OK) {
Intent intent = new Intent(this, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_START, true);
Log.i(TAG, "Start service=" + intent);
Intent intent = new Intent(ActivityMain.this, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_COMMAND, BlackHoleService.Command.start);
startService(intent);
}
} else

View file

@ -5,10 +5,10 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.VpnService;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.preference.PreferenceManager;
import android.util.Log;
@ -19,47 +19,50 @@ import java.io.IOException;
public class BlackHoleService extends VpnService implements Runnable {
private static final String TAG = "NetGuard.BlackHole";
private Thread thread;
private Thread thread = null;
public static final String EXTRA_COMMAND = "Command";
public static final String EXTRA_START = "Start";
@Override
public IBinder onBind(Intent intent) {
return null;
}
public enum Command {start, reload, stop}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Start intent=" + intent);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean enabled = prefs.getBoolean("enabled", false);
if (thread != null)
thread.interrupt();
Command cmd = (intent == null ? Command.start : (Command) intent.getSerializableExtra(EXTRA_COMMAND));
Log.i(TAG, "Start intent=" + intent + " command=" + cmd + " enabled=" + enabled + " running=" + (thread != null));
boolean enabled = (intent != null &&
intent.hasExtra(EXTRA_START) &&
intent.getBooleanExtra(EXTRA_START, false));
if (enabled) {
Log.i(TAG, "Starting");
thread = new Thread(this, "BlackHoleThread");
thread.start();
if (cmd == Command.reload || cmd == Command.stop) {
if (thread != null) {
Log.i(TAG, "Stopping");
thread.interrupt();
}
if (cmd == Command.stop)
stopSelf();
}
if (cmd == Command.start || cmd == Command.reload) {
if (enabled && (thread == null || thread.isInterrupted())) {
Log.i(TAG, "Starting");
thread = new Thread(this, "BlackHoleThread");
thread.start();
}
}
// TODO: check if start sticky is enough to keep the VPN service alive
return START_STICKY;
}
private BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("enabled", false))
if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_TYPE) &&
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY) == ConnectivityManager.TYPE_WIFI) {
Intent service = new Intent(BlackHoleService.this, BlackHoleService.class);
service.putExtra(BlackHoleService.EXTRA_START, true);
Log.i(TAG, "Start service=" + service);
startService(service);
}
Log.i(TAG, "Received " + intent);
Util.logExtras(TAG, intent);
if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_TYPE) &&
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY) == ConnectivityManager.TYPE_WIFI) {
Intent service = new Intent(BlackHoleService.this, BlackHoleService.class);
service.putExtra(BlackHoleService.EXTRA_COMMAND, Command.reload);
startService(service);
}
}
};
@ -86,11 +89,13 @@ public class BlackHoleService extends VpnService implements Runnable {
Log.i(TAG, "Revoke");
if (thread != null)
thread.interrupt();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", false).apply();
super.onRevoke();
}
@Override
public void run() {
public synchronized void run() {
Log.i(TAG, "Run");
ParcelFileDescriptor pfd = null;
try {
@ -124,11 +129,13 @@ public class BlackHoleService extends VpnService implements Runnable {
// Drop all packets
Log.i(TAG, "Loop start");
FileInputStream in = new FileInputStream(pfd.getFileDescriptor());
while (!thread.isInterrupted())
while (!Thread.currentThread().isInterrupted())
in.skip(32768);
Log.i(TAG, "Loop exit");
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
} finally {
if (pfd != null)
try {
@ -136,8 +143,6 @@ public class BlackHoleService extends VpnService implements Runnable {
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
thread = null;
stopSelf();
}
}
}

View file

@ -3,9 +3,7 @@ package eu.faircode.netguard;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.VpnService;
import android.preference.PreferenceManager;
import android.util.Log;
public class BootReceiver extends BroadcastReceiver {
@ -15,13 +13,10 @@ public class BootReceiver extends BroadcastReceiver {
public void onReceive(final Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("enabled", false))
if (VpnService.prepare(context) == null) {
Intent service = new Intent(context, BlackHoleService.class);
service.putExtra(BlackHoleService.EXTRA_START, true);
Log.i(TAG, "Start service=" + service);
context.startService(service);
}
if (VpnService.prepare(context) == null) {
Intent service = new Intent(context, BlackHoleService.class);
service.putExtra(BlackHoleService.EXTRA_COMMAND, BlackHoleService.Command.start);
context.startService(service);
}
}
}
}

View file

@ -46,7 +46,7 @@ public class Rule implements Comparable<Rule> {
@Override
public int compareTo(Rule other) {
int i = this.name.compareTo(other.name);
return (i == 0 ? this.info.packageName.compareTo(other.info.packageName) : i);
int i = name.compareToIgnoreCase(other.name);
return (i == 0 ? info.packageName.compareTo(other.info.packageName) : i);
}
}

View file

@ -3,7 +3,6 @@ package eu.faircode.netguard;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@ -31,12 +30,12 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> {
public ViewHolder(View itemView) {
super(itemView);
this.view = itemView;
this.ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
this.tvName = (TextView) itemView.findViewById(R.id.tvName);
this.tvPackage = (TextView) itemView.findViewById(R.id.tvPackage);
this.cbWifi = (CheckBox) itemView.findViewById(R.id.cbWifi);
this.cbOther = (CheckBox) itemView.findViewById(R.id.cbOther);
view = itemView;
ivIcon = (ImageView) itemView.findViewById(R.id.ivIcon);
tvName = (TextView) itemView.findViewById(R.id.tvName);
tvPackage = (TextView) itemView.findViewById(R.id.tvPackage);
cbWifi = (CheckBox) itemView.findViewById(R.id.cbWifi);
cbOther = (CheckBox) itemView.findViewById(R.id.cbOther);
}
}
@ -45,65 +44,53 @@ public class RuleAdapter extends RecyclerView.Adapter<RuleAdapter.ViewHolder> {
}
@Override
public RuleAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.rule, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
public void onBindViewHolder(final ViewHolder holder, int position) {
final Rule rule = listRule.get(position);
holder.ivIcon.setImageDrawable(rule.getIcon(holder.view.getContext()));
holder.tvName.setText(rule.name);
holder.tvPackage.setText(rule.info.packageName);
CompoundButton.OnCheckedChangeListener cbListener = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
String name;
if (buttonView == holder.cbWifi) {
name = "wifi";
rule.wifi_blocked = isChecked;
} else {
name = "other";
rule.other_blocked = isChecked;
}
Log.i(TAG, rule.info.packageName + ": " + name + "=" + isChecked);
Context context = buttonView.getContext();
SharedPreferences prefs = context.getSharedPreferences(name, Context.MODE_PRIVATE);
prefs.edit().putBoolean(rule.info.packageName, isChecked).apply();
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_COMMAND, BlackHoleService.Command.reload);
context.startService(intent);
}
};
holder.cbWifi.setOnCheckedChangeListener(null);
holder.cbWifi.setChecked(rule.wifi_blocked);
holder.cbWifi.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
Context context = compoundButton.getContext();
rule.wifi_blocked = isChecked;
Log.i(TAG, rule.info.packageName + "=" + rule.wifi_blocked);
SharedPreferences prefs = context.getSharedPreferences("wifi", Context.MODE_PRIVATE);
prefs.edit().putBoolean(rule.info.packageName, rule.wifi_blocked).apply();
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("enabled", false)) {
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_START, true);
context.startService(intent);
}
}
});
holder.cbWifi.setOnCheckedChangeListener(cbListener);
holder.cbOther.setOnCheckedChangeListener(null);
holder.cbOther.setChecked(rule.other_blocked);
holder.cbOther.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
Context context = compoundButton.getContext();
holder.cbOther.setOnCheckedChangeListener(cbListener);
}
rule.other_blocked = isChecked;
Log.i(TAG, rule.info.packageName + "=" + rule.other_blocked);
SharedPreferences prefs = context.getSharedPreferences("other", Context.MODE_PRIVATE);
prefs.edit().putBoolean(rule.info.packageName, rule.other_blocked).apply();
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("enabled", false)) {
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(BlackHoleService.EXTRA_START, true);
context.startService(intent);
}
}
});
@Override
public RuleAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.rule, parent, false));
}
@Override
public int getItemCount() {
return this.listRule.size();
return listRule.size();
}
}

View file

@ -0,0 +1,35 @@
package eu.faircode.netguard;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import java.util.Set;
public class Util {
public static String getSelfVersionName(Context context) {
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return pInfo.versionName;
} catch (PackageManager.NameNotFoundException ex) {
return ex.toString();
}
}
public static void logExtras(String tag, Intent intent) {
logBundle(tag, intent.getExtras());
}
public static void logBundle(String tag, Bundle data) {
if (data != null) {
Set<String> keys = data.keySet();
StringBuilder stringBuilder = new StringBuilder();
for (String key : keys)
stringBuilder.append(key).append("=").append(data.get(key)).append("\r\n");
Log.d(tag, stringBuilder.toString());
}
}
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textAppearance="@android:style/TextAppearance.Material.Medium"
android:textStyle="bold" />
<TextView
android:id="@+id/tvVersion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:textAppearance="@android:style/TextAppearance.Material.Medium"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/app_copyright"
android:textAppearance="@android:style/TextAppearance.Material.Small" />
</LinearLayout>

View file

@ -6,5 +6,6 @@
<Switch
android:id="@+id/swEnabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_marginLeft="16dp" />
</RelativeLayout>

View file

@ -2,8 +2,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingBottom="6dp">
android:layout_marginBottom="4dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/ivIcon"
@ -15,9 +15,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="6dp">
android:orientation="vertical">
<TextView
android:id="@+id/tvName"
@ -41,14 +41,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:button="@drawable/wifi"
android:paddingLeft="3dp" />
android:layout_marginLeft="12dp"
android:button="@drawable/wifi" />
<CheckBox
android:id="@+id/cbOther"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:button="@drawable/other"
android:paddingLeft="3dp" />
android:layout_marginLeft="12dp"
android:button="@drawable/other" />
</LinearLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_vpn_settings"
android:title="@string/menu_vpn_settings" />
<item
android:id="@+id/menu_about"
android:title="@string/menu_about" />
</menu>

View file

@ -1,5 +1,4 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View file

@ -1,3 +1,6 @@
<resources>
<string name="app_name">NetGuard for Android</string>
<string name="app_name">NetGuard</string>
<string name="app_copyright">Copyright \u00A9 2015 by M. Bokhorst (M66B)</string>
<string name="menu_vpn_settings">VPN settings</string>
<string name="menu_about">About</string>
</resources>

View file

@ -1,7 +1,5 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>