summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java239
1 files changed, 216 insertions, 23 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
index ff375553..792de2cb 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java
@@ -1,43 +1,88 @@
package se.leap.bitmaskclient.eip;
-import android.content.*;
-import android.net.*;
-import android.os.*;
+import android.annotation.TargetApi;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.util.Log;
-import java.io.*;
+import java.io.IOException;
+import java.util.Observable;
+import java.util.Observer;
-import se.leap.bitmaskclient.Constants;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.VpnStatus;
+import se.leap.bitmaskclient.Dashboard;
+import se.leap.bitmaskclient.R;
-public class VoidVpnService extends VpnService {
+import static android.os.Build.VERSION_CODES.O;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_EIP;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_BLOCKING_VPN;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN;
+import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON;
+import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
+
+
+public class VoidVpnService extends VpnService implements Observer {
static final String TAG = VoidVpnService.class.getSimpleName();
static ParcelFileDescriptor fd;
-
static Thread thread;
+ private final int ALWAYS_ON_MIN_API_LEVEL = Build.VERSION_CODES.N;
+ private static final String STATE_ESTABLISH = "ESTABLISHVOIDVPN";
+ public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "bitmask_void_vpn_news";
+ private EipStatus eipStatus;
+ NotificationManager notificationManager;
+ NotificationManagerCompat compatNotificationManager;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ compatNotificationManager = NotificationManagerCompat.from(this);
+ eipStatus = EipStatus.getInstance();
+ eipStatus.addObserver(this);
+ }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : "";
- if (action == Constants.EIP_ACTION_BLOCK_VPN_PROFILE) {
+ if (action.equals(EIP_ACTION_START_BLOCKING_VPN)) {
+ thread = new Thread(new Runnable() {
+ public void run() {
+ establishBlockingVpn();
+ SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().putBoolean(EIP_IS_ALWAYS_ON, false).commit();
+ Log.d(TAG, "start blocking vpn profile - always on = false");
+ }
+ });
+ thread.run();
+ } else if (action.equals("android.net.VpnService") && Build.VERSION.SDK_INT >= ALWAYS_ON_MIN_API_LEVEL) {
+ //only always-on feature triggers this
thread = new Thread(new Runnable() {
public void run() {
- Builder builder = new Builder();
- builder.setSession("Blocking until running");
- builder.addAddress("10.42.0.8", 16);
- builder.addRoute("0.0.0.0", 1);
- builder.addRoute("192.168.1.0", 24);
- builder.addDnsServer("10.42.0.1");
- try {
- fd = builder.establish();
-
- } catch (Exception e) {
- e.printStackTrace();
- }
+ establishBlockingVpn();
+ SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences.edit().putBoolean(EIP_IS_ALWAYS_ON, true).commit();
+ requestVpnWithLastSelectedProfile();
+ Log.d(TAG, "start blocking vpn profile - always on = true");
}
});
thread.run();
+ } else if (action.equals(EIP_ACTION_STOP_BLOCKING_VPN)) {
+ stop();
}
- return 0;
+ return START_STICKY;
}
@Override
@@ -46,9 +91,27 @@ public class VoidVpnService extends VpnService {
closeFd();
}
- public static void stop() {
- if (thread != null)
+ @TargetApi(O)
+ private void createNotificationChannel() {
+
+ // Connection status change messages
+ CharSequence name = getString(R.string.channel_name_status);
+ NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManagerCompat.IMPORTANCE_DEFAULT);
+
+ mChannel.setDescription(getString(R.string.channel_description_status));
+ mChannel.enableLights(true);
+
+ mChannel.setLightColor(Color.BLUE);
+ notificationManager.createNotificationChannel(mChannel);
+ }
+
+
+ private void stop() {
+ stopNotifications();
+ if (thread != null) {
thread.interrupt();
+ }
closeFd();
}
@@ -64,4 +127,134 @@ public class VoidVpnService extends VpnService {
e.printStackTrace();
}
}
+
+ private Builder prepareBlockingVpnProfile() {
+ Builder builder = new Builder();
+ builder.setSession("Blocking until running");
+ builder.addRoute("0.0.0.0", 1);
+ builder.addRoute("192.168.1.0", 24);
+ builder.addDnsServer("10.42.0.1");
+ builder.addAddress("10.42.0.8", 16);
+ return builder;
+
+ }
+
+ private void establishBlockingVpn() {
+ try {
+ VpnStatus.logInfo(getString(R.string.void_vpn_establish));
+ VpnStatus.updateStateString(STATE_ESTABLISH, "",
+ R.string.void_vpn_establish, ConnectionStatus.LEVEL_BLOCKING);
+ Builder builder = prepareBlockingVpnProfile();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ builder.addDisallowedApplication(getPackageName());
+ }
+
+ fd = builder.establish();
+ } catch (Exception e) {
+ // Catch any exception
+ e.printStackTrace();
+ VpnStatus.logError(R.string.void_vpn_error_establish);
+ }
+ }
+
+ private void requestVpnWithLastSelectedProfile() {
+ Intent startEIP = new Intent(getApplicationContext(), EIP.class);
+ startEIP.setAction(EIP_ACTION_START_ALWAYS_ON_EIP);
+ getApplicationContext().startService(startEIP);
+ }
+
+ @Override
+ public void update(Observable observable, Object arg) {
+ if (observable instanceof EipStatus) {
+ eipStatus = (EipStatus) observable;
+ }
+
+ if (thread == null) {
+ return;
+ }
+
+ if (eipStatus.isBlockingVpnEstablished()) {
+ showNotification(getString(eipStatus.getLocalizedResId()),
+ getString(eipStatus.getLocalizedResId()), eipStatus.getLevel());
+ } else {
+ stopNotifications();
+ }
+ }
+
+ private void stopNotifications() {
+ stopForeground(true);
+ compatNotificationManager.cancel(NOTIFICATION_CHANNEL_NEWSTATUS_ID.hashCode());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+ notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID) != null) {
+ notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+ }
+ }
+
+ /**
+ * @param msg
+ * @param tickerText
+ * @param status
+ */
+ private void showNotification(final String msg, String tickerText, ConnectionStatus status) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ createNotificationChannel();
+ }
+
+ int icon = getIconByConnectionStatus(status);
+ NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_NEWSTATUS_ID);
+
+ nCompatBuilder.setContentTitle(getString(R.string.notifcation_title_bitmask, getString(R.string.void_vpn_title)));
+ nCompatBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE);
+ nCompatBuilder.setLocalOnly(true);
+ nCompatBuilder.setContentText(msg);
+ nCompatBuilder.setOnlyAlertOnce(true);
+ nCompatBuilder.setSmallIcon(icon);
+ if (tickerText != null && !tickerText.equals("")) {
+ nCompatBuilder.setTicker(tickerText);
+ }
+
+ nCompatBuilder.setContentIntent(getDashboardIntent());
+ //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN
+ NotificationCompat.Action.Builder builder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel, getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent());
+ nCompatBuilder.addAction(builder.build());
+
+ Notification notification = nCompatBuilder.build();
+ int notificationId = NOTIFICATION_CHANNEL_NEWSTATUS_ID.hashCode();
+ compatNotificationManager.notify(notificationId, notification);
+ startForeground(notificationId, notification);
+ }
+
+ private PendingIntent getDashboardIntent() {
+ Intent startDashboard = new Intent(this, Dashboard.class);
+ startDashboard.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return PendingIntent.getActivity(this, 0, startDashboard, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent getStopVoidVpnIntent() {
+ Intent stopVoidVpnIntent = new Intent (this, VoidVpnService.class);
+ stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
+ return PendingIntent.getService(this, 0, stopVoidVpnIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ //TODO: replace with getIconByEipLevel(EipLevel level)
+ private int getIconByConnectionStatus(ConnectionStatus level) {
+ switch (level) {
+ case LEVEL_CONNECTED:
+ return R.drawable.ic_stat_vpn;
+ case LEVEL_AUTH_FAILED:
+ case LEVEL_NONETWORK:
+ case LEVEL_NOTCONNECTED:
+ return R.drawable.ic_stat_vpn_offline;
+ case LEVEL_CONNECTING_NO_SERVER_REPLY_YET:
+ case LEVEL_WAITING_FOR_USER_INPUT:
+ case LEVEL_CONNECTING_SERVER_REPLIED:
+ return R.drawable.ic_stat_vpn_outline;
+ case LEVEL_BLOCKING:
+ return R.drawable.ic_stat_vpn_blocking;
+ case UNKNOWN_LEVEL:
+ default:
+ return R.drawable.ic_stat_vpn_offline;
+ }
+ }
}