package eu.faircode.netguard;
/*
This file is part of NetGuard.
NetGuard is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
NetGuard is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with NetGuard. If not, see .
Copyright 2015-2016 by Marcel Bokhorst (M66B)
*/
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.net.VpnService;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SwitchCompat;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class ActivityMain extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "NetGuard.Main";
private boolean running = false;
private SwipeRefreshLayout swipeRefresh;
private RuleAdapter adapter = null;
private MenuItem menuSearch = null;
private AlertDialog dialogFirst = null;
private AlertDialog dialogVpn = null;
private AlertDialog dialogAbout = null;
private static final int REQUEST_VPN = 1;
private static final int REQUEST_INVITE = 2;
private static final int REQUEST_LOGCAT = 3;
public static final int REQUEST_ROAMING = 4;
private static final int MIN_SDK = Build.VERSION_CODES.LOLLIPOP;
public static final String ACTION_RULES_CHANGED = "eu.faircode.netguard.ACTION_RULES_CHANGED";
public static final String EXTRA_SEARCH = "Search";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "Create");
if (Build.VERSION.SDK_INT < MIN_SDK) {
super.onCreate(savedInstanceState);
setContentView(R.layout.android);
return;
}
Util.setTheme(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
running = true;
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean enabled = prefs.getBoolean("enabled", false);
boolean initialized = prefs.getBoolean("initialized", false);
// Upgrade
Receiver.upgrade(initialized, this);
if (enabled)
SinkholeService.start("UI", this);
else
SinkholeService.stop("UI", this);
// Action bar
View actionView = getLayoutInflater().inflate(R.layout.action, null);
SwitchCompat swEnabled = (SwitchCompat) actionView.findViewById(R.id.swEnabled);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setCustomView(actionView);
// On/off switch
swEnabled.setChecked(enabled);
swEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
prefs.edit().putBoolean("enabled", isChecked).apply();
if (isChecked) {
Log.i(TAG, "Switch on");
try {
final Intent prepare = VpnService.prepare(ActivityMain.this);
if (prepare == null) {
Log.i(TAG, "Prepare done");
onActivityResult(REQUEST_VPN, RESULT_OK, null);
} else {
// Show dialog
LayoutInflater inflater = LayoutInflater.from(ActivityMain.this);
View view = inflater.inflate(R.layout.vpn, null);
dialogVpn = new AlertDialog.Builder(ActivityMain.this)
.setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (running) {
Log.i(TAG, "Start intent=" + prepare);
try {
startActivityForResult(prepare, REQUEST_VPN);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, ActivityMain.this);
onActivityResult(REQUEST_VPN, RESULT_CANCELED, null);
prefs.edit().putBoolean("enabled", false).apply();
}
}
}
})
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
dialogVpn = null;
}
})
.create();
dialogVpn.show();
}
} catch (Throwable ex) {
// Prepare failed
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, ActivityMain.this);
prefs.edit().putBoolean("enabled", false).apply();
}
} else {
Log.i(TAG, "Switch off");
prefs.edit().putBoolean("enabled", false).apply();
SinkholeService.stop("switch off", ActivityMain.this);
}
}
});
// Disabled warning
TextView tvDisabled = (TextView) findViewById(R.id.tvDisabled);
tvDisabled.setVisibility(enabled ? View.GONE : View.VISIBLE);
// Application list
RecyclerView rvApplication = (RecyclerView) findViewById(R.id.rvApplication);
rvApplication.setHasFixedSize(true);
rvApplication.setLayoutManager(new LinearLayoutManager(this));
adapter = new RuleAdapter(this);
rvApplication.setAdapter(adapter);
// Swipe to refresh
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, tv, true);
swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefresh);
swipeRefresh.setColorSchemeColors(Color.WHITE, Color.WHITE, Color.WHITE);
swipeRefresh.setProgressBackgroundColorSchemeColor(tv.data);
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
SinkholeService.reload(null, "pull", ActivityMain.this);
updateApplicationList(null);
}
});
// Listen for preference changes
prefs.registerOnSharedPreferenceChangeListener(this);
// Listen for rule set changes
IntentFilter iff = new IntentFilter(ACTION_RULES_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(onRulesetChanged, iff);
// Listen for added/removed applications
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
registerReceiver(packageChangedReceiver, intentFilter);
// First use
if (!initialized) {
// Create view
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.first, null);
TextView tvFirst = (TextView) view.findViewById(R.id.tvFirst);
tvFirst.setMovementMethod(LinkMovementMethod.getInstance());
// Show dialog
dialogFirst = new AlertDialog.Builder(this)
.setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (running)
prefs.edit().putBoolean("initialized", true).apply();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
dialogFirst = null;
}
})
.create();
dialogFirst.show();
}
// Fill application list
updateApplicationList(getIntent().getStringExtra(EXTRA_SEARCH));
// Update IAB SKUs
try {
new IAB(new IAB.Delegate() {
@Override
public void onReady(IAB iab) {
try {
iab.updatePurchases();
if (!IAB.isPurchased(ActivityPro.SKU_LOG, ActivityMain.this))
prefs.edit().putBoolean("log", false).apply();
if (!IAB.isPurchased(ActivityPro.SKU_THEME, ActivityMain.this)) {
if (!"teal".equals(prefs.getString("theme", "teal")))
prefs.edit().putString("theme", "teal").apply();
}
if (!IAB.isPurchased(ActivityPro.SKU_SPEED, ActivityMain.this))
prefs.edit().putBoolean("show_stats", false).apply();
iab.unbind();
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
}, this).bind();
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, ActivityMain.this);
}
}
@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, "New intent");
super.onNewIntent(intent);
updateApplicationList(intent.getStringExtra(EXTRA_SEARCH));
}
@Override
public void onDestroy() {
Log.i(TAG, "Destroy");
if (Build.VERSION.SDK_INT < MIN_SDK) {
super.onDestroy();
return;
}
running = false;
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
LocalBroadcastManager.getInstance(this).unregisterReceiver(onRulesetChanged);
unregisterReceiver(packageChangedReceiver);
if (dialogFirst != null) {
dialogFirst.dismiss();
dialogFirst = null;
}
if (dialogVpn != null) {
dialogVpn.dismiss();
dialogVpn = null;
}
if (dialogAbout != null) {
dialogAbout.dismiss();
dialogAbout = null;
}
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
Log.i(TAG, "onActivityResult request=" + requestCode + " result=" + requestCode + " ok=" + (resultCode == RESULT_OK));
Util.logExtras(data);
if (requestCode == REQUEST_VPN) {
// Handle VPN approval
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", resultCode == RESULT_OK).apply();
if (resultCode == RESULT_OK)
SinkholeService.start("prepared", this);
} else if (requestCode == REQUEST_INVITE) {
// Do nothing
} else if (requestCode == REQUEST_LOGCAT) {
// Send logcat by e-mail
if (resultCode == RESULT_OK)
Util.sendLogcat(data.getData(), this);
} else {
Log.w(TAG, "Unknown activity result request=" + requestCode);
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == REQUEST_ROAMING)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
SinkholeService.reload("other", "permission granted", this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String name) {
Log.i(TAG, "Preference " + name + "=" + prefs.getAll().get(name));
if ("enabled".equals(name)) {
// Get enabled
boolean enabled = prefs.getBoolean(name, false);
// Display disabled warning
TextView tvDisabled = (TextView) findViewById(R.id.tvDisabled);
tvDisabled.setVisibility(enabled ? View.GONE : View.VISIBLE);
// Check switch state
SwitchCompat swEnabled = (SwitchCompat) getSupportActionBar().getCustomView().findViewById(R.id.swEnabled);
if (swEnabled.isChecked() != enabled)
swEnabled.setChecked(enabled);
} else if ("whitelist_wifi".equals(name) ||
"screen_wifi".equals(name) ||
"whitelist_other".equals(name) ||
"screen_other".equals(name) ||
"whitelist_roaming".equals(name) ||
"show_user".equals(name) ||
"show_system".equals(name) ||
"show_nointernet".equals(name) ||
"show_disabled".equals(name) ||
"sort".equals(name) ||
"imported".equals(name)) {
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuSearch);
updateApplicationList(menuSearch.isActionViewExpanded() ? searchView.getQuery().toString() : null);
} else if ("manage_system".equals(name)) {
invalidateOptionsMenu();
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuSearch);
updateApplicationList(menuSearch.isActionViewExpanded() ? searchView.getQuery().toString() : null);
} else if ("theme".equals(name) || "dark_theme".equals(name))
recreate();
}
private BroadcastReceiver onRulesetChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(intent);
if (adapter != null)
if (intent.hasExtra("connected") && intent.hasExtra("metered"))
if (intent.getBooleanExtra("connected", false))
if (intent.getBooleanExtra("metered", false))
adapter.setMobileActive();
else
adapter.setWifiActive();
else
adapter.setDisconnected();
else {
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuSearch);
updateApplicationList(menuSearch.isActionViewExpanded() ? searchView.getQuery().toString() : null);
}
}
};
private BroadcastReceiver packageChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(intent);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menuSearch);
updateApplicationList(menuSearch.isActionViewExpanded() ? searchView.getQuery().toString() : null);
}
};
private void updateApplicationList(final String search) {
Log.i(TAG, "Update search=" + search);
new AsyncTask