NetGuard/app/src/main/java/eu/faircode/netguard/SinkholeService.java

1762 lines
71 KiB
Java
Raw Normal View History

2015-10-24 18:01:55 +00:00
package eu.faircode.netguard;
2015-11-03 17:57:29 +00:00
/*
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 <http://www.gnu.org/licenses/>.
2016-01-01 13:52:09 +00:00
Copyright 2015-2016 by Marcel Bokhorst (M66B)
2015-11-03 17:57:29 +00:00
*/
import android.annotation.TargetApi;
2016-01-01 09:06:02 +00:00
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
2015-10-24 18:01:55 +00:00
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
2016-01-30 13:26:30 +00:00
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
2015-10-24 18:01:55 +00:00
import android.net.ConnectivityManager;
import android.net.TrafficStats;
2015-10-24 18:01:55 +00:00
import android.net.VpnService;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
2015-10-24 18:01:55 +00:00
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
2016-01-22 15:13:33 +00:00
import android.os.Process;
import android.os.SystemClock;
2015-10-24 19:50:29 +00:00
import android.preference.PreferenceManager;
2015-10-30 09:51:44 +00:00
import android.support.v4.app.NotificationCompat;
2015-11-04 20:15:57 +00:00
import android.support.v4.app.NotificationManagerCompat;
2015-11-04 20:13:17 +00:00
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
2016-01-31 08:04:54 +00:00
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
2015-10-24 18:01:55 +00:00
import android.util.Log;
2016-01-02 12:22:18 +00:00
import android.util.TypedValue;
import android.widget.RemoteViews;
2015-10-24 18:01:55 +00:00
2016-01-28 10:58:39 +00:00
import java.io.BufferedReader;
2016-01-17 09:42:21 +00:00
import java.io.File;
2016-01-28 10:58:39 +00:00
import java.io.FileReader;
2015-10-24 18:01:55 +00:00
import java.io.IOException;
import java.net.InetAddress;
2016-01-30 09:59:19 +00:00
import java.net.UnknownHostException;
2016-01-30 18:52:31 +00:00
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
2015-12-13 13:49:08 +00:00
import java.util.Comparator;
2016-01-01 09:06:02 +00:00
import java.util.Date;
import java.util.HashMap;
2015-12-10 07:38:45 +00:00
import java.util.HashSet;
import java.util.List;
2016-01-30 13:26:30 +00:00
import java.util.Map;
2015-12-10 07:38:45 +00:00
import java.util.Set;
import java.util.TreeMap;
2015-10-24 18:01:55 +00:00
2016-01-09 08:00:18 +00:00
public class SinkholeService extends VpnService implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "NetGuard.Service";
2015-10-24 18:01:55 +00:00
private State state = State.none;
2016-01-11 10:38:48 +00:00
private boolean user_foreground = true;
private boolean last_connected = false;
private boolean last_metered = true;
2016-01-01 09:06:02 +00:00
private boolean last_interactive = false;
2016-01-28 22:07:51 +00:00
private boolean last_tethering = false;
private boolean last_filter = false;
private String last_vpn4 = null;
private String last_vpn6 = null;
2016-01-30 09:59:19 +00:00
private InetAddress last_dns = null;
private boolean phone_state = false;
2015-12-07 11:23:43 +00:00
private Object subscriptionsChangedListener = null;
private ParcelFileDescriptor vpn = null;
2015-10-29 22:29:01 +00:00
2016-01-30 13:26:30 +00:00
private Map<String, Boolean> mapHostsBlocked = new HashMap<>();
private Map<Integer, Boolean> mapUidAllowed = new HashMap<>();
private Map<Long, Map<InetAddress, Boolean>> mapUidIPFilters = new HashMap<>();
2016-02-08 15:34:54 +00:00
private Map<Integer, Forward> mapForward = new HashMap<>();
2016-01-28 10:58:39 +00:00
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private static final int NOTIFY_ENFORCING = 1;
private static final int NOTIFY_WAITING = 2;
private static final int NOTIFY_DISABLED = 3;
2016-02-02 07:20:57 +00:00
private static final int NOTIFY_AUTOSTART = 4;
private static final int NOTIFY_ERROR = 5;
private static final int NOTIFY_TRAFFIC = 6;
public static final String EXTRA_COMMAND = "Command";
private static final String EXTRA_REASON = "Reason";
public static final String EXTRA_NETWORK = "Network";
public static final String EXTRA_UID = "UID";
public static final String EXTRA_PACKAGE = "Package";
public static final String EXTRA_BLOCKED = "Blocked";
2015-10-24 18:01:55 +00:00
private static final int MSG_SERVICE_INTENT = 0;
private static final int MSG_STATS_START = 1;
private static final int MSG_STATS_STOP = 2;
private static final int MSG_STATS_UPDATE = 3;
2016-01-22 09:54:48 +00:00
private static final int MSG_PACKET = 4;
2016-02-03 16:09:12 +00:00
private static final int MSG_RR = 5;
private enum State {none, waiting, enforcing, stats}
2016-01-09 08:00:18 +00:00
public enum Command {run, start, reload, stop, stats, set}
2015-10-24 18:01:55 +00:00
private static volatile PowerManager.WakeLock wlInstance = null;
2016-01-01 09:06:02 +00:00
private static final String ACTION_SCREEN_OFF_DELAYED = "eu.faircode.netguard.SCREEN_OFF_DELAYED";
private native void jni_init();
2016-01-14 14:02:32 +00:00
private native void jni_start(int tun, boolean fwd53, int loglevel);
2016-01-09 11:10:11 +00:00
2016-01-17 05:35:26 +00:00
private native void jni_stop(int tun, boolean clear);
2016-01-09 15:56:23 +00:00
2016-02-08 17:43:29 +00:00
private native int[] jni_get_session_count();
2016-01-31 17:46:44 +00:00
private static native void jni_pcap(String name);
private native void jni_done();
2016-01-31 17:46:44 +00:00
public static void setPcap(File pcap) {
jni_pcap(pcap == null ? null : pcap.getAbsolutePath());
}
synchronized private static PowerManager.WakeLock getLock(Context context) {
if (wlInstance == null) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
2015-11-14 11:17:57 +00:00
wlInstance = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, context.getString(R.string.app_name) + " wakelock");
wlInstance.setReferenceCounted(true);
}
return wlInstance;
}
private final class ServiceHandler extends Handler {
2016-01-20 10:04:32 +00:00
private boolean stats = false;
private long when;
private long t = -1;
private long tx = -1;
private long rx = -1;
private List<Long> gt = new ArrayList<>();
private List<Float> gtx = new ArrayList<>();
private List<Float> grx = new ArrayList<>();
private HashMap<Integer, Long> mapUidBytes = new HashMap<>();
2016-01-20 10:04:32 +00:00
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
switch (msg.what) {
case MSG_SERVICE_INTENT:
handleIntent((Intent) msg.obj);
break;
case MSG_STATS_START:
startStats();
break;
case MSG_STATS_STOP:
stopStats();
break;
case MSG_STATS_UPDATE:
updateStats();
break;
2016-01-22 09:54:48 +00:00
case MSG_PACKET:
log((Packet) msg.obj);
break;
2016-02-03 16:09:12 +00:00
case MSG_RR:
resolved((ResourceRecord) msg.obj);
break;
}
2015-11-23 07:34:47 +00:00
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, SinkholeService.this);
} finally {
if (msg.what == MSG_SERVICE_INTENT)
try {
PowerManager.WakeLock wl = getLock(SinkholeService.this);
if (wl.isHeld())
wl.release();
else
Log.w(TAG, "Wakelock under-locked");
Log.i(TAG, "Messages=" + hasMessages(0) + " wakelock=" + wlInstance.isHeld());
} catch (Exception ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
Util.sendCrashReport(ex, SinkholeService.this);
}
}
}
private void handleIntent(Intent intent) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
2015-11-24 09:42:45 +00:00
Command cmd = (Command) intent.getSerializableExtra(EXTRA_COMMAND);
String reason = intent.getStringExtra(EXTRA_REASON);
2016-01-11 10:38:48 +00:00
Log.i(TAG, "Executing intent=" + intent + " command=" + cmd + " reason=" + reason +
2016-01-22 15:13:33 +00:00
" vpn=" + (vpn != null) + " user=" + (Process.myUid() / 100000));
// Check if prepared
if (cmd == Command.start || cmd == Command.reload)
if (VpnService.prepare(SinkholeService.this) != null) {
Log.w(TAG, "VPN not prepared");
2016-02-02 07:20:57 +00:00
prefs.edit().putBoolean("enabled", false).apply();
showAutoStartNotification();
return;
}
2016-01-11 10:38:48 +00:00
// Check if foreground
if (cmd != Command.stop)
if (!user_foreground) {
Log.i(TAG, "Command " + cmd + "ignored for background user");
2016-01-28 22:07:51 +00:00
return;
2016-01-11 10:38:48 +00:00
}
2015-12-07 09:46:35 +00:00
// Listen for phone state changes
2015-12-01 16:42:38 +00:00
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null && !phone_state &&
2015-12-07 09:46:35 +00:00
Util.hasPhoneStatePermission(SinkholeService.this)) {
tm.listen(phoneStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | PhoneStateListener.LISTEN_SERVICE_STATE);
2015-12-01 16:42:38 +00:00
phone_state = true;
Log.i(TAG, "Listening to service state changes");
}
2015-12-07 09:46:35 +00:00
// Listen for data SIM changes
2015-12-07 11:23:43 +00:00
if (subscriptionsChangedListener == null &&
2015-12-07 09:46:35 +00:00
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 &&
Util.hasPhoneStatePermission(SinkholeService.this)) {
SubscriptionManager sm = SubscriptionManager.from(SinkholeService.this);
2015-12-15 07:38:07 +00:00
subscriptionsChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() {
2015-12-07 11:23:43 +00:00
@Override
public void onSubscriptionsChanged() {
Log.i(TAG, "Subscriptions changed");
2016-01-20 10:04:32 +00:00
SinkholeService.reload(null, "Subscriptions changed", SinkholeService.this);
2015-12-07 11:23:43 +00:00
}
};
sm.addOnSubscriptionsChangedListener((SubscriptionManager.OnSubscriptionsChangedListener) subscriptionsChangedListener);
2015-12-07 09:46:35 +00:00
Log.i(TAG, "Listening to subscription changes");
}
try {
switch (cmd) {
case run:
2016-01-20 10:04:32 +00:00
run();
break;
case start:
2016-01-20 10:04:32 +00:00
start();
break;
case reload:
2016-01-20 10:04:32 +00:00
reload();
2016-01-30 09:59:19 +00:00
cleanupDNS();
break;
case stop:
2016-01-20 10:04:32 +00:00
stop();
break;
2015-12-11 09:36:35 +00:00
case stats:
stopStats();
startStats();
break;
case set:
set(intent);
break;
}
2015-11-26 07:34:33 +00:00
2015-11-27 12:48:48 +00:00
// Update main view
2015-11-26 07:34:33 +00:00
Intent ruleset = new Intent(ActivityMain.ACTION_RULES_CHANGED);
ruleset.putExtra("connected", last_connected);
ruleset.putExtra("metered", last_metered);
LocalBroadcastManager.getInstance(SinkholeService.this).sendBroadcast(ruleset);
2015-11-27 12:48:48 +00:00
// Update widgets
Widget.updateWidgets(SinkholeService.this);
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
if (!(ex instanceof IllegalStateException)) {
// Disable firewall
prefs.edit().putBoolean("enabled", false).apply();
Widget.updateWidgets(SinkholeService.this);
// Report exception
Util.sendCrashReport(ex, SinkholeService.this);
}
}
}
2016-01-20 10:04:32 +00:00
private void run() {
if (state == State.none) {
startForeground(NOTIFY_WAITING, getWaitingNotification());
state = State.waiting;
Log.d(TAG, "Start foreground state=" + state.toString());
}
}
2016-01-20 10:04:32 +00:00
private void start() {
if (vpn == null) {
if (state != State.none) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
}
2016-02-01 18:43:17 +00:00
startForeground(NOTIFY_ENFORCING, getEnforcingNotification(0, 0, 0));
2016-01-20 10:04:32 +00:00
state = State.enforcing;
Log.d(TAG, "Start foreground state=" + state.toString());
2016-01-20 10:04:32 +00:00
List<Rule> listRule = Rule.getRules(true, TAG, SinkholeService.this);
List<Rule> listAllowed = getAllowedRules(listRule);
2016-01-20 10:04:32 +00:00
vpn = startVPN(listAllowed);
if (vpn == null)
throw new IllegalStateException("VPN start failed");
startNative(vpn, listAllowed);
2016-01-20 10:04:32 +00:00
2016-01-23 18:00:35 +00:00
removeWarningNotifications();
2016-01-20 10:04:32 +00:00
updateEnforcingNotification(listAllowed.size(), listRule.size());
}
}
private void reload() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean tethering = prefs.getBoolean("tethering", false);
boolean filter = prefs.getBoolean("filter", false);
String vpn4 = prefs.getString("vpn4", "10.1.10.1");
String vpn6 = prefs.getString("vpn6", "fd00:1:fd00:1:fd00:1:fd00:1");
2016-01-30 09:59:19 +00:00
InetAddress dns = getDns(SinkholeService.this);
2016-01-20 10:04:32 +00:00
if (state != State.enforcing) {
if (state != State.none) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
}
2016-02-01 18:43:17 +00:00
startForeground(NOTIFY_ENFORCING, getEnforcingNotification(0, 0, 0));
2016-01-20 10:04:32 +00:00
state = State.enforcing;
Log.d(TAG, "Start foreground state=" + state.toString());
}
List<Rule> listRule = Rule.getRules(true, TAG, SinkholeService.this);
List<Rule> listAllowed = getAllowedRules(listRule);
if (filter &&
2016-01-28 22:07:51 +00:00
filter == last_filter &&
tethering == last_tethering &&
vpn4.equals(last_vpn4) &&
vpn6.equals(last_vpn6) &&
dns.equals(last_dns)) {
Log.i(TAG, "Native restart");
if (vpn != null)
2016-01-28 22:07:51 +00:00
stopNative(vpn, false);
2016-01-20 10:04:32 +00:00
if (vpn == null)
vpn = startVPN(listAllowed);
if (vpn == null)
throw new IllegalStateException("VPN start failed");
startNative(vpn, listAllowed);
2016-01-20 10:04:32 +00:00
} else {
Log.i(TAG, "VPN restart");
2016-01-20 10:04:32 +00:00
// Attempt seamless handover
ParcelFileDescriptor prev = vpn;
vpn = startVPN(listAllowed);
if (prev != null && vpn == null) {
Log.w(TAG, "Handover failed");
stopVPN(prev);
prev = null;
vpn = startVPN(listAllowed);
if (vpn == null)
throw new IllegalStateException("Handover failed");
}
if (prev != null) {
2016-01-28 22:07:51 +00:00
stopNative(prev, false);
stopVPN(prev);
}
startNative(vpn, listAllowed);
}
2016-01-20 10:04:32 +00:00
updateEnforcingNotification(listAllowed.size(), listRule.size());
}
private void stop() {
if (vpn != null) {
2016-01-28 22:07:51 +00:00
stopNative(vpn, true);
2016-01-20 10:04:32 +00:00
stopVPN(vpn);
vpn = null;
}
if (state == State.enforcing) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
if (prefs.getBoolean("show_stats", false)) {
startForeground(NOTIFY_WAITING, getWaitingNotification());
state = State.waiting;
Log.d(TAG, "Start foreground state=" + state.toString());
} else
state = State.none;
}
}
private void startStats() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean enabled = (!stats && prefs.getBoolean("show_stats", false));
Log.i(TAG, "Stats start enabled=" + enabled);
if (enabled) {
when = new Date().getTime();
t = -1;
tx = -1;
rx = -1;
gt.clear();
gtx.clear();
grx.clear();
mapUidBytes.clear();
stats = true;
updateStats();
}
}
private void stopStats() {
Log.i(TAG, "Stats stop");
stats = false;
2015-12-09 16:57:10 +00:00
mServiceHandler.removeMessages(MSG_STATS_UPDATE);
if (state == State.stats) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
state = State.none;
} else
NotificationManagerCompat.from(SinkholeService.this).cancel(NOTIFY_TRAFFIC);
}
private void updateStats() {
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.traffic);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
2015-12-15 07:38:07 +00:00
long frequency = Long.parseLong(prefs.getString("stats_frequency", "1000"));
long samples = Long.parseLong(prefs.getString("stats_samples", "90"));
float base = Long.parseLong(prefs.getString("stats_base", "5")) * 1000f;
2016-02-08 17:43:29 +00:00
boolean filter = prefs.getBoolean("filter", false);
boolean show_top = prefs.getBoolean("show_top", false);
int loglevel = Integer.parseInt(prefs.getString("loglevel", Integer.toString(Log.WARN)));
// Schedule next update
mServiceHandler.sendEmptyMessageDelayed(MSG_STATS_UPDATE, frequency);
long ct = SystemClock.elapsedRealtime();
// Cleanup
while (gt.size() > 0 && ct - gt.get(0) > samples * 1000) {
gt.remove(0);
gtx.remove(0);
grx.remove(0);
}
// Calculate network speed
float txsec = 0;
float rxsec = 0;
long ttx = TrafficStats.getTotalTxBytes();
long trx = TrafficStats.getTotalRxBytes();
2016-02-08 17:43:29 +00:00
if (filter) {
ttx -= TrafficStats.getUidTxBytes(Process.myUid());
trx -= TrafficStats.getUidRxBytes(Process.myUid());
}
2015-12-09 16:57:10 +00:00
if (t > 0 && tx > 0 && rx > 0) {
float dt = (ct - t) / 1000f;
txsec = (ttx - tx) / dt;
rxsec = (trx - rx) / dt;
gt.add(ct);
gtx.add(txsec);
grx.add(rxsec);
}
// Calculate application speeds
2016-02-08 17:43:29 +00:00
if (show_top) {
if (mapUidBytes.size() == 0) {
for (ApplicationInfo ainfo : getPackageManager().getInstalledApplications(0))
2016-01-22 15:13:33 +00:00
if (ainfo.uid != Process.myUid())
mapUidBytes.put(ainfo.uid, TrafficStats.getUidTxBytes(ainfo.uid) + TrafficStats.getUidRxBytes(ainfo.uid));
2016-01-21 16:46:31 +00:00
} else if (t > 0) {
TreeMap<Float, Integer> mapSpeedUid = new TreeMap<>(new Comparator<Float>() {
2015-12-13 13:49:08 +00:00
@Override
public int compare(Float value, Float other) {
return -value.compareTo(other);
}
});
float dt = (ct - t) / 1000f;
for (int uid : mapUidBytes.keySet()) {
long bytes = TrafficStats.getUidTxBytes(uid) + TrafficStats.getUidRxBytes(uid);
float speed = (bytes - mapUidBytes.get(uid)) / dt;
if (speed > 0) {
mapSpeedUid.put(speed, uid);
mapUidBytes.put(uid, bytes);
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
for (float speed : mapSpeedUid.keySet()) {
if (i++ >= 3)
break;
if (speed < 1000 * 1000)
sb.append(getString(R.string.msg_kbsec, speed / 1000));
else
sb.append(getString(R.string.msg_mbsec, speed / 1000 / 1000));
sb.append(' ');
List<String> apps = Util.getApplicationNames(mapSpeedUid.get(speed), SinkholeService.this);
sb.append(apps.size() > 0 ? apps.get(0) : "?");
sb.append("\r\n");
}
if (sb.length() > 0)
sb.setLength(sb.length() - 2);
remoteViews.setTextViewText(R.id.tvTop, sb.toString());
}
}
t = ct;
tx = ttx;
rx = trx;
// Create bitmap
2015-12-09 17:38:35 +00:00
int height = Util.dips2pixels(96, SinkholeService.this);
2015-12-09 18:25:40 +00:00
int width = Util.dips2pixels(96 * 5, SinkholeService.this);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// Create canvas
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.TRANSPARENT);
// Determine max
long xmax = 0;
2015-12-10 09:29:17 +00:00
float ymax = base * 1.5f;
for (int i = 0; i < gt.size(); i++) {
long t = gt.get(i);
float tx = gtx.get(i);
float rx = grx.get(i);
if (t > xmax)
xmax = t;
if (tx > ymax)
ymax = tx;
if (rx > ymax)
ymax = rx;
}
// Build paths
Path ptx = new Path();
Path prx = new Path();
for (int i = 0; i < gtx.size(); i++) {
float x = width - width * (xmax - gt.get(i)) / 1000f / samples;
float ytx = height - height * gtx.get(i) / ymax;
float yrx = height - height * grx.get(i) / ymax;
if (i == 0) {
ptx.moveTo(x, ytx);
prx.moveTo(x, yrx);
} else {
ptx.lineTo(x, ytx);
prx.lineTo(x, yrx);
}
}
// Build paint
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.STROKE);
2015-12-10 06:56:08 +00:00
// Draw base line
paint.setStrokeWidth(Util.dips2pixels(1, SinkholeService.this));
paint.setColor(Color.GRAY);
2015-12-09 16:43:27 +00:00
float y = height - height * base / ymax;
canvas.drawLine(0, y, width, y, paint);
2015-12-11 17:59:08 +00:00
// Draw paths
paint.setStrokeWidth(Util.dips2pixels(2, SinkholeService.this));
paint.setColor(ContextCompat.getColor(SinkholeService.this, R.color.colorSend));
canvas.drawPath(ptx, paint);
paint.setColor(ContextCompat.getColor(SinkholeService.this, R.color.colorReceive));
canvas.drawPath(prx, paint);
// Update remote view
remoteViews.setImageViewBitmap(R.id.ivTraffic, bitmap);
2015-12-09 19:05:25 +00:00
if (txsec < 1000 * 1000)
remoteViews.setTextViewText(R.id.tvTx, getString(R.string.msg_kbsec, txsec / 1000));
2015-12-09 16:57:10 +00:00
else
2015-12-09 19:05:25 +00:00
remoteViews.setTextViewText(R.id.tvTx, getString(R.string.msg_mbsec, txsec / 1000 / 1000));
if (rxsec < 1000 * 1000)
remoteViews.setTextViewText(R.id.tvRx, getString(R.string.msg_kbsec, rxsec / 1000));
else
remoteViews.setTextViewText(R.id.tvRx, getString(R.string.msg_mbsec, rxsec / 1000 / 1000));
2016-02-08 17:43:29 +00:00
if (filter && loglevel <= Log.WARN) {
int[] count = jni_get_session_count();
2016-02-09 14:29:28 +00:00
remoteViews.setTextViewText(R.id.tvSessions, count[0] + "/" + count[1] + "/" + count[2]);
2016-02-08 17:43:29 +00:00
} else
remoteViews.setTextViewText(R.id.tvSessions, "");
// Show notification
Intent main = new Intent(SinkholeService.this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(SinkholeService.this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
2016-01-02 12:22:18 +00:00
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, tv, true);
NotificationCompat.Builder builder = new NotificationCompat.Builder(SinkholeService.this)
.setWhen(when)
.setSmallIcon(R.drawable.ic_equalizer_white_24dp)
.setContent(remoteViews)
.setContentIntent(pi)
2016-01-02 12:22:18 +00:00
.setColor(tv.data)
.setOngoing(true)
.setAutoCancel(false);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_PUBLIC);
}
if (state == State.none || state == State.waiting) {
if (state != State.none) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
}
startForeground(NOTIFY_TRAFFIC, builder.build());
state = State.stats;
Log.d(TAG, "Start foreground state=" + state.toString());
} else
NotificationManagerCompat.from(SinkholeService.this).notify(NOTIFY_TRAFFIC, builder.build());
}
2016-01-22 09:54:48 +00:00
private void log(Packet packet) {
2016-01-31 08:04:54 +00:00
// Get settings
2016-01-30 11:46:00 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
2016-01-31 08:04:54 +00:00
boolean log = prefs.getBoolean("log", false);
boolean log_app = prefs.getBoolean("log_app", false);
2016-01-31 08:04:54 +00:00
boolean notify = prefs.getBoolean("notify_access", false);
boolean system = prefs.getBoolean("manage_system", false);
DatabaseHelper dh = DatabaseHelper.getInstance(SinkholeService.this);
2016-01-30 11:46:00 +00:00
2016-02-03 16:59:24 +00:00
// Get real name
String dname = dh.getQName(packet.daddr);
2016-01-31 09:29:43 +00:00
// Traffic log
2016-01-31 08:04:54 +00:00
if (log)
dh.insertLog(packet, dname, (last_connected ? last_metered ? 2 : 1 : 0), last_interactive);
2016-01-30 11:46:00 +00:00
2016-01-31 09:29:43 +00:00
// Application log
if (log_app && packet.uid >= 0) {
2016-02-06 09:04:42 +00:00
if (!(packet.protocol == 6 /* TCP */ || packet.protocol == 17 /* UDP */))
packet.dport = 0;
if (dh.updateAccess(packet, dname, -1))
if (notify && prefs.getBoolean("notify_" + packet.uid, true) &&
(system || !Util.isSystem(packet.uid, SinkholeService.this)))
showAccessNotification(packet.uid);
}
if (packet.uid < 0 && packet.dport != 53)
Log.w(TAG, "Unknown application packet " + packet);
2016-01-22 09:54:48 +00:00
}
2016-02-03 16:09:12 +00:00
private void resolved(ResourceRecord rr) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
if (prefs.getBoolean("resolved", true))
DatabaseHelper.getInstance(SinkholeService.this).insertDns(rr);
2016-02-03 16:09:12 +00:00
}
private void set(Intent intent) {
// Get arguments
int uid = intent.getIntExtra(EXTRA_UID, 0);
String network = intent.getStringExtra(EXTRA_NETWORK);
String pkg = intent.getStringExtra(EXTRA_PACKAGE);
boolean blocked = intent.getBooleanExtra(EXTRA_BLOCKED, false);
Log.i(TAG, "Set " + pkg + " " + network + "=" + blocked);
2015-12-25 18:40:59 +00:00
// Get defaults
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean default_wifi = settings.getBoolean("whitelist_wifi", true);
boolean default_other = settings.getBoolean("whitelist_other", true);
// Update setting
SharedPreferences prefs = getSharedPreferences(network, Context.MODE_PRIVATE);
if (blocked == ("wifi".equals(network) ? default_wifi : default_other))
prefs.edit().remove(pkg).apply();
else
prefs.edit().putBoolean(pkg, blocked).apply();
// Apply rules
2016-01-20 10:04:32 +00:00
SinkholeService.reload(null, "notification", SinkholeService.this);
2015-12-25 18:40:59 +00:00
// Update notification
2016-01-03 16:54:34 +00:00
Receiver.notifyNewApplication(uid, SinkholeService.this);
// Update UI
Intent ruleset = new Intent(ActivityMain.ACTION_RULES_CHANGED);
LocalBroadcastManager.getInstance(SinkholeService.this).sendBroadcast(ruleset);
}
2015-10-24 18:01:55 +00:00
}
2016-01-30 09:59:19 +00:00
public static InetAddress getDns(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String sysDns = Util.getDefaultDNS(context);
String vpnDns = prefs.getString("dns", sysDns);
Log.i(TAG, "DNS system=" + sysDns + " VPN=" + vpnDns);
try {
2016-01-26 07:03:59 +00:00
if (TextUtils.isEmpty(vpnDns.trim()))
2016-01-30 16:08:30 +00:00
throw new UnknownHostException("dns");
InetAddress dns = InetAddress.getByName(vpnDns);
if (dns.isAnyLocalAddress() || dns.isLinkLocalAddress() || dns.isLoopbackAddress())
throw new UnknownHostException("dns");
Log.i(TAG, "DNS using=" + dns);
return dns;
} catch (Throwable ignored) {
try {
InetAddress def = InetAddress.getByName("8.8.8.8");
Log.i(TAG, "DNS using=" + def);
return def;
} catch (UnknownHostException ignored1) {
return null;
}
}
}
2016-01-22 15:13:33 +00:00
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
2016-01-19 19:58:51 +00:00
private ParcelFileDescriptor startVPN(List<Rule> listAllowed) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean tethering = prefs.getBoolean("tethering", false);
boolean filter = prefs.getBoolean("filter", false);
2016-01-28 22:07:51 +00:00
last_filter = filter;
last_tethering = tethering;
last_vpn4 = prefs.getString("vpn4", "10.1.10.1");
last_vpn6 = prefs.getString("vpn6", "fd00:1:fd00:1:fd00:1:fd00:1");
2016-01-30 09:59:19 +00:00
last_dns = getDns(SinkholeService.this);
2016-01-19 19:58:51 +00:00
// Build VPN service
final Builder builder = new Builder();
builder.setSession(getString(R.string.app_name) + " session");
// VPN address
Log.i(TAG, "vpn4=" + last_vpn4 + " vpn6=" + last_vpn6);
builder.addAddress(last_vpn4, 32);
builder.addAddress(last_vpn6, 64);
2016-01-23 15:58:25 +00:00
if (filter)
builder.addDnsServer(last_dns);
2016-01-19 19:58:51 +00:00
if (tethering) {
// USB Tethering 192.168.42.x
// Wi-Fi Tethering 192.168.43.x
2016-01-24 14:52:45 +00:00
// https://en.wikipedia.org/wiki/IPv4#Special-use_addresses
for (int r = 1; r <= 223; r++)
2016-01-19 19:58:51 +00:00
if (r == 192) {
for (int s = 1; s <= 255; s++)
if (s == 168) {
for (int t = 1; t <= 255; t++)
if (t != 42 && t != 43)
builder.addRoute(String.format("%d.%d.%d.0", r, s, t), 24);
} else
builder.addRoute(String.format("%d.%d.0.0", r, s), 16);
2016-01-24 14:52:45 +00:00
} else if (r != 127)
2016-01-19 19:58:51 +00:00
builder.addRoute(String.format("%d.0.0.0", r), 8);
} else
builder.addRoute("0.0.0.0", 0);
builder.addRoute("0:0:0:0:0:0:0:0", 0);
builder.setMtu(32768);
2016-01-19 19:58:51 +00:00
// Add list of allowed applications
if (last_connected && !filter)
for (Rule rule : listAllowed)
try {
builder.addDisallowedApplication(rule.info.packageName);
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
// Build configure intent
Intent configure = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, configure, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setConfigureIntent(pi);
// Start VPN service
try {
return builder.establish();
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
return null;
}
}
private void startNative(ParcelFileDescriptor vpn, List<Rule> listAllowed) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean log = prefs.getBoolean("log", false);
boolean filter = prefs.getBoolean("filter", false);
2016-01-28 22:07:51 +00:00
Log.i(TAG, "Start native log=" + log + " filter=" + filter);
2016-01-30 13:26:30 +00:00
// Prepare rules
2016-01-31 16:16:56 +00:00
if (filter) {
prepareUidAllowed(listAllowed);
prepareHostsBlocked();
2016-02-03 16:59:24 +00:00
prepareUidIPFilters();
2016-02-08 15:34:54 +00:00
prepareForwarding();
2016-01-31 16:16:56 +00:00
} else
unprepare();
2016-01-28 22:07:51 +00:00
if (log || filter) {
2016-02-08 17:43:29 +00:00
int prio = Integer.parseInt(prefs.getString("loglevel", Integer.toString(Log.WARN)));
2016-02-08 15:34:54 +00:00
jni_start(vpn.getFd(), mapForward.containsKey(53), prio);
2016-01-28 10:58:39 +00:00
}
2016-01-31 16:16:56 +00:00
// Native needs to be started for name resolving
if (filter)
2016-02-03 16:59:24 +00:00
new Thread(new Runnable() {
@Override
public void run() {
updateUidIPFilters();
}
}).start();
2016-01-28 10:58:39 +00:00
}
2016-01-28 22:07:51 +00:00
private void stopNative(ParcelFileDescriptor vpn, boolean clear) {
Log.i(TAG, "Stop native clear=" + clear);
jni_stop(vpn.getFd(), clear);
}
2016-01-31 16:16:56 +00:00
private void unprepare() {
mapUidAllowed.clear();
mapHostsBlocked.clear();
mapUidIPFilters.clear();
2016-02-08 15:34:54 +00:00
mapForward.clear();
2016-01-31 16:16:56 +00:00
}
2016-01-30 13:26:30 +00:00
private void prepareUidAllowed(List<Rule> listAllowed) {
2016-01-28 10:58:39 +00:00
mapUidAllowed.clear();
for (Rule rule : listAllowed)
mapUidAllowed.put(rule.info.applicationInfo.uid, true);
2016-01-30 13:26:30 +00:00
}
private void prepareHostsBlocked() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
boolean use_hosts = prefs.getBoolean("use_hosts", false);
2016-01-30 16:01:53 +00:00
File hosts = new File(getFilesDir(), "hosts.txt");
2016-01-28 10:58:39 +00:00
2016-01-30 13:26:30 +00:00
mapHostsBlocked.clear();
2016-01-30 16:01:53 +00:00
if (use_hosts && hosts.exists() && hosts.canRead()) {
int count = 0;
2016-01-28 10:58:39 +00:00
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(hosts));
String line;
while ((line = br.readLine()) != null) {
int hash = line.indexOf('#');
if (hash >= 0)
line = line.substring(0, hash);
line = line.trim();
if (line.length() > 0) {
String[] words = line.split("\\s+");
2016-01-30 16:01:53 +00:00
if (words.length == 2) {
count++;
2016-01-30 13:26:30 +00:00
mapHostsBlocked.put(words[1], true);
2016-01-30 16:01:53 +00:00
} else
2016-01-28 10:58:39 +00:00
Log.i(TAG, "Invalid hosts file line: " + line);
}
}
2016-01-30 16:01:53 +00:00
Log.i(TAG, count + " hosts read");
2016-01-28 10:58:39 +00:00
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
} finally {
if (br != null)
try {
br.close();
} catch (IOException exex) {
Log.e(TAG, exex.toString() + "\n" + Log.getStackTraceString(exex));
}
}
}
}
2016-01-30 13:26:30 +00:00
private void prepareUidIPFilters() {
Map<Long, Map<InetAddress, Boolean>> map = new HashMap<>();
2016-01-30 13:26:30 +00:00
DatabaseHelper dh = DatabaseHelper.getInstance(SinkholeService.this);
2016-02-03 16:59:24 +00:00
Cursor cursor = dh.getDns();
2016-01-30 20:35:24 +00:00
int colUid = cursor.getColumnIndex("uid");
int colVersion = cursor.getColumnIndex("version");
int colProtocol = cursor.getColumnIndex("protocol");
2016-02-03 18:50:12 +00:00
int colDAddr = cursor.getColumnIndex("daddr");
2016-02-03 16:59:24 +00:00
int colResource = cursor.getColumnIndex("resource");
2016-01-30 20:35:24 +00:00
int colDPort = cursor.getColumnIndex("dport");
int colBlock = cursor.getColumnIndex("block");
while (cursor.moveToNext()) {
int uid = cursor.getInt(colUid);
int version = cursor.getInt(colVersion);
int protocol = cursor.getInt(colProtocol);
2016-02-03 18:50:12 +00:00
String daddr = cursor.getString(colDAddr);
2016-02-03 16:59:24 +00:00
String dresource = cursor.getString(colResource);
int dport = cursor.getInt(colDPort);
2016-01-30 20:35:24 +00:00
boolean block = (cursor.getInt(colBlock) > 0);
2016-01-30 17:43:20 +00:00
// long is 64 bits
// 0..15 uid
// 16..31 dport
// 32..39 protocol
// 40..43 version
2016-02-06 09:04:42 +00:00
if (!(protocol == 6 /* TCP */ || protocol == 17 /* UDP */))
dport = 0;
long key = (version << 40) | (protocol << 32) | (dport << 16) | uid;
2016-01-30 20:35:24 +00:00
if (!map.containsKey(key))
map.put(key, new HashMap());
try {
map.get(key).put(InetAddress.getByName(dresource), block);
Log.i(TAG, "Set filter uid=" + uid + " " + daddr + " " + dresource + "/" + dport + "=" + block);
} catch (UnknownHostException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
2016-02-03 16:59:24 +00:00
}
}
cursor.close();
synchronized (mapUidIPFilters) {
mapUidIPFilters = map;
}
}
private void updateUidIPFilters() {
DatabaseHelper dh = DatabaseHelper.getInstance(SinkholeService.this);
2016-02-03 16:59:24 +00:00
Cursor cursor = dh.getAccess();
int colDAddr = cursor.getColumnIndex("daddr");
while (cursor.moveToNext()) {
String daddr = cursor.getString(colDAddr);
2016-01-30 20:35:24 +00:00
try {
2016-02-03 18:35:12 +00:00
// This will result in native callbacks
InetAddress.getAllByName(daddr);
2016-01-30 20:35:24 +00:00
} catch (UnknownHostException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
2016-01-30 13:26:30 +00:00
}
}
2016-01-30 20:35:24 +00:00
cursor.close();
2016-01-30 13:26:30 +00:00
}
2016-02-08 15:34:54 +00:00
private void prepareForwarding() {
mapForward.clear();
DatabaseHelper dh = DatabaseHelper.getInstance(SinkholeService.this);
2016-02-08 15:34:54 +00:00
2016-02-09 09:53:52 +00:00
Cursor cursor = dh.getForwarding();
2016-02-08 15:34:54 +00:00
int colProtocol = cursor.getColumnIndex("protocol");
int colDPort = cursor.getColumnIndex("dport");
int colRAddr = cursor.getColumnIndex("raddr");
int colRPort = cursor.getColumnIndex("rport");
int colRUid = cursor.getColumnIndex("ruid");
while (cursor.moveToNext()) {
Forward fwd = new Forward();
fwd.protocol = cursor.getInt(colProtocol);
fwd.dport = cursor.getInt(colDPort);
fwd.raddr = cursor.getString(colRAddr);
fwd.rport = cursor.getInt(colRPort);
fwd.ruid = cursor.getInt(colRUid);
mapForward.put(fwd.dport, fwd);
Log.i(TAG, "Forward " + fwd);
}
cursor.close();
}
2016-02-03 16:59:24 +00:00
private void cleanupDNS() {
2016-02-03 19:38:38 +00:00
// Keep records for a week
DatabaseHelper.getInstance(SinkholeService.this).cleanupDns(new Date().getTime() - 7 * 24 * 3600 * 1000L);
2016-02-03 16:59:24 +00:00
}
2016-01-19 19:58:51 +00:00
private List<Rule> getAllowedRules(List<Rule> listRule) {
List<Rule> listAllowed = new ArrayList<>();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
// Check state
2015-11-13 10:05:10 +00:00
boolean wifi = Util.isWifiActive(this);
2015-11-13 07:07:15 +00:00
boolean metered = Util.isMeteredNetwork(this);
boolean useMetered = prefs.getBoolean("use_metered", false);
2015-12-10 07:38:45 +00:00
Set<String> ssidHomes = prefs.getStringSet("wifi_homes", new HashSet<String>());
2015-12-08 13:18:06 +00:00
String ssidNetwork = Util.getWifiSSID(this);
String generation = Util.getNetworkGeneration(this);
2015-12-02 19:11:06 +00:00
boolean unmetered_2g = prefs.getBoolean("unmetered_2g", false);
boolean unmetered_3g = prefs.getBoolean("unmetered_3g", false);
boolean unmetered_4g = prefs.getBoolean("unmetered_4g", false);
boolean roaming = Util.isRoaming(SinkholeService.this);
boolean national = prefs.getBoolean("national_roaming", false);
boolean tethering = prefs.getBoolean("tethering", false);
boolean filter = prefs.getBoolean("filter", false);
2015-11-25 20:09:00 +00:00
// Update connected state
last_connected = Util.isConnected(SinkholeService.this);
// Update metered state
if (wifi && !useMetered)
2015-11-25 20:09:00 +00:00
metered = false;
if (wifi && ssidHomes.size() > 0 && !ssidHomes.contains(ssidNetwork)) {
2015-12-08 13:18:06 +00:00
metered = true;
Log.i(TAG, "Not at home");
}
2015-12-02 19:11:06 +00:00
if (unmetered_2g && "2G".equals(generation))
metered = false;
2015-12-02 19:11:06 +00:00
if (unmetered_3g && "3G".equals(generation))
metered = false;
2015-12-02 19:11:06 +00:00
if (unmetered_4g && "4G".equals(generation))
metered = false;
2015-11-25 20:09:00 +00:00
last_metered = metered;
// Update roaming state
if (roaming && national)
roaming = Util.isInternational(this);
Log.i(TAG, "Get allowed" +
" connected=" + last_connected +
2015-11-25 20:09:00 +00:00
" wifi=" + wifi +
" home=" + TextUtils.join(",", ssidHomes) +
" network=" + ssidNetwork +
2015-11-25 20:09:00 +00:00
" metered=" + metered +
" generation=" + generation +
" roaming=" + roaming +
" interactive=" + last_interactive +
" tethering=" + tethering +
" filter=" + filter);
if (last_connected)
for (Rule rule : listRule) {
boolean blocked = (metered ? rule.other_blocked : rule.wifi_blocked);
boolean screen = (metered ? rule.screen_other : rule.screen_wifi);
if ((!blocked || (screen && last_interactive)) && (!metered || !(rule.roaming && roaming)))
listAllowed.add(rule);
}
2016-01-19 19:58:51 +00:00
2016-02-10 18:52:55 +00:00
Log.i(TAG, "Allowed " + listAllowed.size() + " of " + listRule.size());
2016-01-19 19:58:51 +00:00
return listAllowed;
}
2015-10-30 07:57:36 +00:00
private void stopVPN(ParcelFileDescriptor pfd) {
Log.i(TAG, "Stopping");
try {
2015-10-26 13:32:14 +00:00
pfd.close();
2015-10-25 18:02:33 +00:00
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
2015-11-20 09:10:22 +00:00
Util.sendCrashReport(ex, this);
}
}
2016-01-21 11:55:08 +00:00
// Called from native code
private void nativeExit(String reason) {
2016-01-24 11:50:40 +00:00
Log.w(TAG, "Native exit reason=" + reason);
if (reason != null) {
2016-01-21 11:55:08 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", false).apply();
2016-01-24 11:50:40 +00:00
showErrorNotification(reason);
2016-01-21 11:55:08 +00:00
}
}
// Called from native code
private void nativeError(String message) {
Log.e(TAG, "Native error message=" + message);
}
2016-01-10 07:35:02 +00:00
// Called from native code
private void logPacket(Packet packet) {
2016-01-30 11:46:00 +00:00
Message msg = mServiceHandler.obtainMessage();
msg.obj = packet;
msg.what = MSG_PACKET;
mServiceHandler.sendMessage(msg);
2016-01-09 18:53:28 +00:00
}
2016-01-30 08:51:41 +00:00
// Called from native code
private void dnsResolved(ResourceRecord rr) {
2016-02-03 16:09:12 +00:00
Message msg = mServiceHandler.obtainMessage();
msg.obj = rr;
msg.what = MSG_RR;
mServiceHandler.sendMessage(msg);
2016-01-30 09:59:19 +00:00
}
2016-01-28 10:58:39 +00:00
// Called from native code
private boolean isDomainBlocked(String name) {
return (mapHostsBlocked.containsKey(name) && mapHostsBlocked.get(name));
2016-01-28 10:58:39 +00:00
}
// Called from native code
private Allowed isAddressAllowed(Packet packet) {
2016-01-28 10:58:39 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
2016-01-31 16:16:56 +00:00
// Allow name resolving
if (packet.uid == Process.myUid())
return new Allowed();
2016-01-31 16:16:56 +00:00
2016-01-30 13:26:30 +00:00
packet.allowed = false;
if (prefs.getBoolean("filter", false)) {
if (packet.uid < 0) // unknown
packet.allowed = true;
else {
boolean filtered = false;
2016-02-06 09:04:42 +00:00
// Only TCP (6) and UDP (17) have port numbers
int dport = (packet.protocol == 6 || packet.protocol == 17 ? packet.dport : 0);
long key = (packet.version << 40) | (packet.protocol << 32) | (dport << 16) | packet.uid;
synchronized (mapUidIPFilters) {
if (mapUidIPFilters.containsKey(key))
try {
InetAddress iaddr = InetAddress.getByName(packet.daddr);
Map<InetAddress, Boolean> map = mapUidIPFilters.get(key);
if (map != null && map.containsKey(iaddr)) {
filtered = true;
packet.allowed = !map.get(iaddr);
Log.i(TAG, "Filtering " + packet);
2016-01-30 17:43:20 +00:00
}
} catch (UnknownHostException ex) {
Log.w(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
2016-01-30 13:26:30 +00:00
}
if (!filtered)
packet.allowed = (mapUidAllowed.containsKey(packet.uid) && mapUidAllowed.get(packet.uid));
2016-01-30 13:26:30 +00:00
}
}
2016-01-28 10:58:39 +00:00
2016-02-08 15:34:54 +00:00
Allowed allowed = null;
if (packet.allowed) {
if (mapForward.containsKey(packet.dport)) {
Forward fwd = mapForward.get(packet.dport);
if (fwd.ruid == packet.uid) {
allowed = new Allowed();
} else {
allowed = new Allowed(fwd.raddr, fwd.rport);
packet.data = "> " + fwd.raddr + "/" + fwd.rport;
2016-02-08 15:34:54 +00:00
}
} else
allowed = new Allowed();
}
2016-02-01 14:44:47 +00:00
if (prefs.getBoolean("log", false) || prefs.getBoolean("log_app", false))
2016-01-28 10:58:39 +00:00
logPacket(packet);
2016-02-08 15:34:54 +00:00
return allowed;
2016-01-28 10:58:39 +00:00
}
private BroadcastReceiver interactiveStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
2015-11-20 10:34:23 +00:00
Util.logExtras(intent);
2016-01-01 09:06:02 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
int delay = Integer.parseInt(prefs.getString("screen_delay", "0"));
boolean interactive = Intent.ACTION_SCREEN_ON.equals(intent.getAction());
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_SCREEN_OFF_DELAYED), PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
if (interactive || delay == 0) {
last_interactive = interactive;
reload(null, "interactive state changed", SinkholeService.this);
} else {
if (ACTION_SCREEN_OFF_DELAYED.equals(intent.getAction())) {
last_interactive = interactive;
reload(null, "interactive state changed", SinkholeService.this);
} else {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
am.set(AlarmManager.RTC_WAKEUP, new Date().getTime() + delay * 60 * 1000L, pi);
else
am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, new Date().getTime() + delay * 60 * 1000L, pi);
}
}
// Start/stop stats
mServiceHandler.sendEmptyMessage(Util.isInteractive(SinkholeService.this) ? MSG_STATS_START : MSG_STATS_STOP);
}
};
2016-01-11 09:08:36 +00:00
private BroadcastReceiver userReceiver = new BroadcastReceiver() {
@Override
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
2016-01-11 09:08:36 +00:00
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(intent);
2016-01-11 10:38:48 +00:00
user_foreground = Intent.ACTION_USER_FOREGROUND.equals(intent.getAction());
2016-01-22 15:13:33 +00:00
Log.i(TAG, "User foreground=" + user_foreground + " user=" + (Process.myUid() / 100000));
2016-01-11 10:38:48 +00:00
if (user_foreground) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
if (prefs.getBoolean("enabled", false)) {
// Allow service of background user to stop
try {
Thread.sleep(3000);
} catch (InterruptedException ignored) {
}
2016-01-11 10:38:48 +00:00
start("foreground", SinkholeService.this);
}
2016-01-11 10:38:48 +00:00
} else
stop("background", SinkholeService.this);
2016-01-11 09:08:36 +00:00
}
};
private BroadcastReceiver idleStateReceiver = new BroadcastReceiver() {
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(intent);
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Log.i(TAG, "device idle=" + pm.isDeviceIdleMode());
2015-11-24 09:42:45 +00:00
// Reload rules when coming from idle mode
if (!pm.isDeviceIdleMode())
reload(null, "idle state changed", SinkholeService.this);
}
};
2015-10-24 18:01:55 +00:00
private BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Filter VPN connectivity changes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY);
if (networkType == ConnectivityManager.TYPE_VPN)
return;
}
2015-11-08 08:40:17 +00:00
// Reload rules
Log.i(TAG, "Received " + intent);
2015-11-20 10:34:23 +00:00
Util.logExtras(intent);
reload(null, "connectivity changed", SinkholeService.this);
2015-10-25 22:31:00 +00:00
}
};
private PhoneStateListener phoneStateListener = new PhoneStateListener() {
private String last_generation = null;
2015-12-06 12:57:02 +00:00
private int last_international = -1;
@Override
public void onDataConnectionStateChanged(int state, int networkType) {
if (state == TelephonyManager.DATA_CONNECTED) {
2015-12-06 12:57:02 +00:00
String current_generation = Util.getNetworkGeneration(SinkholeService.this);
Log.i(TAG, "Data connected generation=" + current_generation);
if (last_generation == null || !last_generation.equals(current_generation)) {
Log.i(TAG, "New network generation=" + current_generation);
last_generation = current_generation;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
2015-12-02 19:11:06 +00:00
if (prefs.getBoolean("unmetered_2g", false) ||
prefs.getBoolean("unmetered_3g", false) ||
prefs.getBoolean("unmetered_4g", false))
reload("other", "data connection state changed", SinkholeService.this);
}
}
}
@Override
public void onServiceStateChanged(ServiceState serviceState) {
if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
2015-12-06 12:57:02 +00:00
int current_international = (Util.isInternational(SinkholeService.this) ? 1 : 0);
Log.i(TAG, "In service international=" + current_international);
2015-12-06 12:57:02 +00:00
if (last_international != current_international) {
Log.i(TAG, "New international=" + current_international);
last_international = current_international;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
if (prefs.getBoolean("national_roaming", false))
reload(null, "service state changed", SinkholeService.this);
}
}
}
};
private BroadcastReceiver packageAddedReceiver = new BroadcastReceiver() {
2015-10-25 22:31:00 +00:00
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
2015-11-20 10:34:23 +00:00
Util.logExtras(intent);
reload(null, "package added", SinkholeService.this);
2015-10-24 18:01:55 +00:00
}
};
@Override
public void onCreate() {
2015-10-24 19:50:29 +00:00
Log.i(TAG, "Create");
2016-01-09 08:00:18 +00:00
2016-01-18 20:07:35 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
// Native init
jni_init();
2016-01-22 12:06:50 +00:00
boolean pcap = prefs.getBoolean("pcap", false);
2016-01-31 17:46:44 +00:00
setPcap(pcap ? new File(getCacheDir(), "netguard.pcap") : null);
2016-01-14 14:02:32 +00:00
2016-01-09 08:00:18 +00:00
prefs.registerOnSharedPreferenceChangeListener(this);
2016-01-02 15:38:24 +00:00
Util.setTheme(this);
super.onCreate();
2016-01-02 12:22:18 +00:00
2015-11-14 11:17:57 +00:00
HandlerThread thread = new HandlerThread(getString(R.string.app_name) + " handler");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// Listen for interactive state changes
2016-01-01 09:06:02 +00:00
last_interactive = Util.isInteractive(this);
IntentFilter ifInteractive = new IntentFilter();
ifInteractive.addAction(Intent.ACTION_SCREEN_ON);
ifInteractive.addAction(Intent.ACTION_SCREEN_OFF);
2016-01-01 09:06:02 +00:00
ifInteractive.addAction(ACTION_SCREEN_OFF_DELAYED);
registerReceiver(interactiveStateReceiver, ifInteractive);
2016-01-11 09:08:36 +00:00
// Listen for user switches
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
IntentFilter ifUser = new IntentFilter();
ifUser.addAction(Intent.ACTION_USER_BACKGROUND);
ifUser.addAction(Intent.ACTION_USER_FOREGROUND);
registerReceiver(userReceiver, ifUser);
}
2016-01-11 09:08:36 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Listen for idle mode state changes
IntentFilter ifIdle = new IntentFilter();
ifIdle.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
registerReceiver(idleStateReceiver, ifIdle);
}
// Listen for connectivity updates
IntentFilter ifConnectivity = new IntentFilter();
ifConnectivity.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(connectivityChangedReceiver, ifConnectivity);
// Listen for added applications
IntentFilter ifPackage = new IntentFilter();
ifPackage.addAction(Intent.ACTION_PACKAGE_ADDED);
ifPackage.addDataScheme("package");
registerReceiver(packageAddedReceiver, ifPackage);
2015-10-24 18:01:55 +00:00
}
2016-01-09 08:00:18 +00:00
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String name) {
if ("theme".equals(name)) {
Log.i(TAG, "Theme changed");
Util.setTheme(this);
if (state != State.none) {
Log.d(TAG, "Stop foreground state=" + state.toString());
stopForeground(true);
}
if (state == State.enforcing)
2016-02-01 18:43:17 +00:00
startForeground(NOTIFY_ENFORCING, getEnforcingNotification(0, 0, 0));
2016-01-09 08:00:18 +00:00
else if (state != State.none)
startForeground(NOTIFY_WAITING, getWaitingNotification());
Log.d(TAG, "Start foreground state=" + state.toString());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
2015-11-28 15:15:55 +00:00
// Keep awake
getLock(this).acquire();
2015-11-24 09:42:45 +00:00
// Handle service restart
if (intent == null) {
2015-11-24 09:54:43 +00:00
Log.i(TAG, "Restart");
2015-11-24 09:42:45 +00:00
// Get enabled
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean enabled = prefs.getBoolean("enabled", false);
// Recreate intent
intent = new Intent(this, SinkholeService.class);
intent.putExtra(EXTRA_COMMAND, enabled ? Command.start : Command.stop);
}
Command cmd = (Command) intent.getSerializableExtra(EXTRA_COMMAND);
String reason = intent.getStringExtra(EXTRA_REASON);
2016-01-11 10:38:48 +00:00
Log.i(TAG, "Start intent=" + intent + " command=" + cmd + " reason=" + reason +
2016-01-22 15:13:33 +00:00
" vpn=" + (vpn != null) + " user=" + (Process.myUid() / 100000));
// Queue command
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = MSG_SERVICE_INTENT;
mServiceHandler.sendMessage(msg);
2015-11-24 09:42:45 +00:00
return START_STICKY;
}
2015-11-24 09:44:02 +00:00
@Override
public void onRevoke() {
Log.i(TAG, "Revoke");
// Disable firewall (will result in stop command)
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", false).apply();
// Feedback
showDisabledNotification();
Widget.updateWidgets(this);
super.onRevoke();
}
2015-10-24 18:01:55 +00:00
@Override
public void onDestroy() {
Log.i(TAG, "Destroy");
2015-10-25 15:28:41 +00:00
2015-11-08 08:40:17 +00:00
mServiceLooper.quit();
2015-10-25 15:28:41 +00:00
unregisterReceiver(interactiveStateReceiver);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
unregisterReceiver(userReceiver);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
unregisterReceiver(idleStateReceiver);
unregisterReceiver(connectivityChangedReceiver);
unregisterReceiver(packageAddedReceiver);
2015-10-25 15:28:41 +00:00
if (phone_state) {
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
tm.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
phone_state = false;
}
2015-12-07 09:46:35 +00:00
}
2015-12-07 11:23:43 +00:00
if (subscriptionsChangedListener != null &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
2015-12-07 09:46:35 +00:00
SubscriptionManager sm = SubscriptionManager.from(this);
2015-12-07 11:23:43 +00:00
sm.removeOnSubscriptionsChangedListener((SubscriptionManager.OnSubscriptionsChangedListener) subscriptionsChangedListener);
subscriptionsChangedListener = null;
}
2016-01-24 07:58:52 +00:00
try {
if (vpn != null) {
2016-01-28 22:07:51 +00:00
stopNative(vpn, true);
2016-01-24 07:58:52 +00:00
stopVPN(vpn);
vpn = null;
}
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
2015-11-08 08:40:17 +00:00
}
2016-01-24 07:58:52 +00:00
2016-01-17 09:42:21 +00:00
jni_done();
2016-01-09 08:00:18 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.unregisterOnSharedPreferenceChangeListener(this);
2015-10-24 18:01:55 +00:00
super.onDestroy();
}
2016-02-01 18:43:17 +00:00
private Notification getEnforcingNotification(int allowed, int blocked, int hosts) {
Intent main = new Intent(this, ActivityMain.class);
2015-11-27 07:23:18 +00:00
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
2016-01-02 12:22:18 +00:00
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, tv, true);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_security_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_started))
.setContentIntent(pi)
2016-01-02 12:22:18 +00:00
.setColor(tv.data)
.setOngoing(true)
.setAutoCancel(false);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET)
.setPriority(Notification.PRIORITY_MIN);
}
if (allowed > 0 || blocked > 0 || hosts > 0) {
NotificationCompat.BigTextStyle notification = new NotificationCompat.BigTextStyle(builder);
notification.bigText(getString(R.string.msg_started));
if (Util.isPlayStoreInstall(this))
notification.setSummaryText(getString(R.string.msg_packages, allowed, blocked));
else
notification.setSummaryText(getString(R.string.msg_hosts, allowed, blocked, hosts));
return notification.build();
} else
return builder.build();
}
2016-01-19 19:58:51 +00:00
private void updateEnforcingNotification(int allowed, int total) {
// Update notification
2016-02-01 18:43:17 +00:00
Notification notification = getEnforcingNotification(allowed, total - allowed, mapHostsBlocked.size());
2016-01-19 19:58:51 +00:00
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify(NOTIFY_ENFORCING, notification);
}
private Notification getWaitingNotification() {
Intent main = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, tv, true);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_security_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_waiting))
.setContentIntent(pi)
.setColor(tv.data)
.setOngoing(true)
.setAutoCancel(false);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET)
.setPriority(Notification.PRIORITY_MIN);
}
return builder.build();
}
2015-11-01 06:44:48 +00:00
private void showDisabledNotification() {
Intent main = new Intent(this, ActivityMain.class);
2015-11-27 07:23:18 +00:00
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
2016-01-02 12:22:18 +00:00
TypedValue tv = new TypedValue();
2016-02-01 06:37:13 +00:00
getTheme().resolveAttribute(R.attr.colorOff, tv, true);
2016-01-23 18:00:35 +00:00
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_error_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_revoked))
.setContentIntent(pi)
2016-01-02 12:22:18 +00:00
.setColor(tv.data)
.setOngoing(false)
.setAutoCancel(true);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET);
}
2016-01-23 18:00:35 +00:00
NotificationCompat.BigTextStyle notification = new NotificationCompat.BigTextStyle(builder);
notification.bigText(getString(R.string.msg_revoked));
2015-11-04 20:15:57 +00:00
NotificationManagerCompat.from(this).notify(NOTIFY_DISABLED, notification.build());
2015-11-01 06:44:48 +00:00
}
2016-02-02 07:20:57 +00:00
private void showAutoStartNotification() {
Intent main = new Intent(this, ActivityMain.class);
main.putExtra(ActivityMain.EXTRA_APPROVE, true);
2016-02-02 07:20:57 +00:00
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
TypedValue tv = new TypedValue();
getTheme().resolveAttribute(R.attr.colorOff, tv, true);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_error_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_autostart))
.setContentIntent(pi)
.setColor(tv.data)
.setOngoing(false)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET);
}
NotificationCompat.BigTextStyle notification = new NotificationCompat.BigTextStyle(builder);
notification.bigText(getString(R.string.msg_autostart));
NotificationManagerCompat.from(this).notify(NOTIFY_AUTOSTART, notification.build());
}
2016-01-24 11:50:40 +00:00
private void showErrorNotification(String reason) {
2016-01-23 18:00:35 +00:00
Intent main = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_UPDATE_CURRENT);
TypedValue tv = new TypedValue();
2016-02-01 06:37:13 +00:00
getTheme().resolveAttribute(R.attr.colorOff, tv, true);
2016-01-23 18:00:35 +00:00
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_error_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_error))
.setContentIntent(pi)
.setColor(tv.data)
.setOngoing(false)
.setAutoCancel(true);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET);
}
2016-01-23 18:00:35 +00:00
NotificationCompat.BigTextStyle notification = new NotificationCompat.BigTextStyle(builder);
notification.bigText(getString(R.string.msg_error));
2016-01-24 11:50:40 +00:00
notification.setSummaryText(reason);
2016-01-23 18:00:35 +00:00
NotificationManagerCompat.from(this).notify(NOTIFY_ERROR, notification.build());
}
2016-01-30 18:52:31 +00:00
private void showAccessNotification(int uid) {
String name = TextUtils.join(", ", Util.getApplicationNames(uid, SinkholeService.this));
Intent main = new Intent(SinkholeService.this, ActivityMain.class);
main.putExtra(ActivityMain.EXTRA_SEARCH, Integer.toString(uid));
2016-01-31 06:43:38 +00:00
PendingIntent pi = PendingIntent.getActivity(SinkholeService.this, uid + 10000, main, PendingIntent.FLAG_UPDATE_CURRENT);
2016-01-30 18:52:31 +00:00
TypedValue tv = new TypedValue();
2016-01-31 08:41:09 +00:00
getTheme().resolveAttribute(R.attr.colorOn, tv, true);
int colorOn = tv.data;
getTheme().resolveAttribute(R.attr.colorOff, tv, true);
int colorOff = tv.data;
2016-01-31 08:04:54 +00:00
2016-01-30 18:52:31 +00:00
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_cloud_upload_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_access, name))
.setContentIntent(pi)
2016-02-01 06:37:13 +00:00
.setColor(colorOff)
2016-01-30 18:52:31 +00:00
.setOngoing(false)
.setAutoCancel(true);
2016-01-31 13:07:07 +00:00
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setCategory(Notification.CATEGORY_STATUS)
2016-01-31 13:07:07 +00:00
.setVisibility(Notification.VISIBILITY_SECRET);
}
2016-01-30 18:52:31 +00:00
DateFormat df = new SimpleDateFormat("dd HH:mm");
NotificationCompat.InboxStyle notification = new NotificationCompat.InboxStyle(builder);
String sname = getString(R.string.msg_access, name);
int pos = sname.indexOf(name);
Spannable sp = new SpannableString(sname);
sp.setSpan(new StyleSpan(Typeface.BOLD), pos, pos + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
notification.addLine(sp);
DatabaseHelper dh = DatabaseHelper.getInstance(SinkholeService.this);
2016-01-30 20:35:24 +00:00
Cursor cursor = dh.getAccessUnset(uid);
2016-01-30 18:52:31 +00:00
int colDAddr = cursor.getColumnIndex("daddr");
2016-01-31 08:04:54 +00:00
int colTime = cursor.getColumnIndex("time");
int colAllowed = cursor.getColumnIndex("allowed");
2016-01-30 18:52:31 +00:00
while (cursor.moveToNext()) {
StringBuilder sb = new StringBuilder();
2016-01-30 18:52:31 +00:00
sb.append(df.format(cursor.getLong(colTime))).append(' ');
String daddr = cursor.getString(colDAddr);
if (Util.isNumericAddress(daddr))
try {
daddr = InetAddress.getByName(daddr).getHostName();
} catch (UnknownHostException ignored) {
}
sb.append(daddr);
2016-01-31 08:04:54 +00:00
int allowed = cursor.getInt(colAllowed);
if (allowed >= 0) {
pos = sb.indexOf(daddr);
sp = new SpannableString(sb);
2016-01-31 08:41:09 +00:00
ForegroundColorSpan fgsp = new ForegroundColorSpan(allowed > 0 ? colorOn : colorOff);
2016-01-31 08:04:54 +00:00
sp.setSpan(fgsp, pos, pos + daddr.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
notification.addLine(sp);
}
2016-01-30 20:35:24 +00:00
cursor.close();
2016-01-30 18:52:31 +00:00
NotificationManagerCompat.from(this).notify(uid + 10000, notification.build());
}
2016-01-23 18:00:35 +00:00
private void removeWarningNotifications() {
2015-11-04 20:15:57 +00:00
NotificationManagerCompat.from(this).cancel(NOTIFY_DISABLED);
2016-01-23 18:00:35 +00:00
NotificationManagerCompat.from(this).cancel(NOTIFY_ERROR);
2015-10-24 18:01:55 +00:00
}
2015-10-25 22:04:10 +00:00
public static void run(String reason, Context context) {
Intent intent = new Intent(context, SinkholeService.class);
intent.putExtra(EXTRA_COMMAND, Command.run);
intent.putExtra(EXTRA_REASON, reason);
context.startService(intent);
}
public static void start(String reason, Context context) {
2015-10-29 06:47:12 +00:00
Intent intent = new Intent(context, SinkholeService.class);
2015-10-26 16:23:41 +00:00
intent.putExtra(EXTRA_COMMAND, Command.start);
intent.putExtra(EXTRA_REASON, reason);
2015-10-26 16:23:41 +00:00
context.startService(intent);
}
public static void reload(String network, String reason, Context context) {
2015-11-04 22:45:47 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("enabled", false)) {
2015-11-17 09:44:08 +00:00
boolean wifi = Util.isWifiActive(context);
boolean metered = Util.isMeteredNetwork(context);
2015-11-17 09:44:08 +00:00
if (wifi && !prefs.getBoolean("use_metered", false))
metered = false;
if (network == null || ("wifi".equals(network) ? !metered : metered)) {
2015-11-04 22:45:47 +00:00
Intent intent = new Intent(context, SinkholeService.class);
intent.putExtra(EXTRA_COMMAND, Command.reload);
intent.putExtra(EXTRA_REASON, reason);
2015-11-04 22:45:47 +00:00
context.startService(intent);
}
}
2015-10-25 22:04:10 +00:00
}
2015-10-26 16:23:41 +00:00
public static void stop(String reason, Context context) {
2015-10-29 06:47:12 +00:00
Intent intent = new Intent(context, SinkholeService.class);
2015-10-26 16:23:41 +00:00
intent.putExtra(EXTRA_COMMAND, Command.stop);
intent.putExtra(EXTRA_REASON, reason);
2015-10-26 16:23:41 +00:00
context.startService(intent);
}
2015-12-11 09:36:35 +00:00
public static void reloadStats(String reason, Context context) {
Intent intent = new Intent(context, SinkholeService.class);
intent.putExtra(EXTRA_COMMAND, Command.stats);
intent.putExtra(EXTRA_REASON, reason);
context.startService(intent);
}
2015-10-24 18:01:55 +00:00
}