From ca1cad6ec5b175a85b361c45e8d2c0cac0b405ec Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 7 Dec 2017 12:49:15 +0100 Subject: #8742 basic always-on implementation with blocking vpn if no profile is configured --- .../java/se/leap/bitmaskclient/eip/Constants.java | 3 + .../main/java/se/leap/bitmaskclient/eip/EIP.java | 30 +++- .../java/se/leap/bitmaskclient/eip/EipStatus.java | 175 ++++++++++++++++----- .../se/leap/bitmaskclient/eip/VoidVpnService.java | 100 +++++++++--- 4 files changed, 244 insertions(+), 64 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/eip') diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java index db1cb4a1..ed4ebcbc 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java @@ -27,6 +27,7 @@ public interface Constants { public final static String ACTION_CHECK_CERT_VALIDITY = TAG + ".CHECK_CERT_VALIDITY"; public final static String ACTION_START_EIP = TAG + ".START_EIP"; + public final static String ACTION_START_ALWAYS_ON_EIP = TAG + ".START_ALWAYS_ON_EIP"; public final static String ACTION_STOP_EIP = TAG + ".STOP_EIP"; public final static String ACTION_UPDATE_EIP_SERVICE = TAG + ".UPDATE_EIP_SERVICE"; public final static String ACTION_IS_EIP_RUNNING = TAG + ".IS_RUNNING"; @@ -40,5 +41,7 @@ public interface Constants { public final static String REQUEST_TAG = TAG + ".REQUEST_TAG"; public final static String START_BLOCKING_VPN_PROFILE = TAG + ".START_BLOCKING_VPN_PROFILE"; public final static String PROVIDER_CONFIGURED = TAG + ".PROVIDER_CONFIGURED"; + public final static String IS_ALWAYS_ON = TAG + ".IS_ALWAYS_ON"; + public final static String RESTART_ON_BOOT = TAG + ".RESTART_ON_BOOT"; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 28a9bb50..39dd133f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -19,6 +19,7 @@ package se.leap.bitmaskclient.eip; import android.app.*; import android.content.*; import android.os.*; +import android.util.Log; import org.json.*; @@ -57,7 +58,6 @@ public final class EIP extends IntentService { @Override public void onCreate() { super.onCreate(); - context = getApplicationContext(); preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); eip_definition = eipDefinitionFromPreferences(); @@ -72,6 +72,8 @@ public final class EIP extends IntentService { if (action.equals(ACTION_START_EIP)) startEIP(); + else if (action.equals(ACTION_START_ALWAYS_ON_EIP)) + startAlwaysOnEIP(); else if (action.equals(ACTION_STOP_EIP)) stopEIP(); else if (action.equals(ACTION_IS_EIP_RUNNING)) @@ -88,9 +90,12 @@ public final class EIP extends IntentService { * It also sets up early routes. */ private void startEIP() { + Log.d(TAG, "startEIP vpn"); if (gateways_manager.isEmpty()) updateEIPService(); - earlyRoutes(); + if (!EipStatus.getInstance().isBlockingVpnEstablished()) { + earlyRoutes(); + } gateway = gateways_manager.select(); if (gateway != null && gateway.getProfile() != null) { @@ -101,6 +106,27 @@ public final class EIP extends IntentService { tellToReceiver(ACTION_START_EIP, Activity.RESULT_CANCELED); } + /** + * Tries to start the last used vpn profile when the OS was rebooted and always-on-VPN is enabled. + * The {@link OnBootReceiver} will care if there is no profile. + */ + private void startAlwaysOnEIP() { + Log.d(TAG, "startAlwaysOnEIP vpn"); + + if (gateways_manager.isEmpty()) + updateEIPService(); + + gateway = gateways_manager.select(); + + if (gateway != null && gateway.getProfile() != null) { + //mReceiver = VpnFragment.getReceiver(); + Log.d(TAG, "startAlwaysOnEIP eip launch avtive gateway vpn"); + launchActiveGateway(); + } else { + Log.d(TAG, "startAlwaysOnEIP no active profile available!"); + } + } + /** * Early routes are routes that block traffic until a new * VpnService is started properly. diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index 501543b8..dc2e81f5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -17,19 +17,37 @@ package se.leap.bitmaskclient.eip; import android.content.*; +import android.os.AsyncTask; +import android.support.annotation.VisibleForTesting; import java.util.*; import de.blinkt.openvpn.core.*; +/** + * EipStatus is a Singleton that represents a reduced set of a vpn's ConnectionStatus. + * EipStatus changes it's state (EipLevel) when ConnectionStatus gets updated by OpenVpnService or + * by VoidVpnService. + */ public class EipStatus extends Observable implements VpnStatus.StateListener { public static String TAG = EipStatus.class.getSimpleName(); private static EipStatus current_status; + public enum EipLevel { + CONNECTING, + DISCONNECTING, + CONNECTED, + DISCONNECTED, + BLOCKING, + UNKNOWN + } - private static ConnectionStatus level = ConnectionStatus.LEVEL_NOTCONNECTED; - private static boolean - wants_to_disconnect = false, - is_connecting = false; + /** + * vpn_level holds the connection status of the openvpn vpn and the traffic blocking + * void vpn. LEVEL_BLOCKING is set when the latter vpn is up. All other states are set by + * openvpn. + */ + private ConnectionStatus vpn_level = ConnectionStatus.LEVEL_NOTCONNECTED; + private EipLevel current_eip_level = EipLevel.DISCONNECTED; int last_error_line = 0; private String state, log_message; @@ -48,64 +66,137 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { @Override public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { - updateStatus(state, logmessage, localizedResId, level); - if (isConnected() || isDisconnected() || wantsToDisconnect()) { - setConnectedOrDisconnected(); - } else - setConnecting(); + current_status = getInstance(); + current_status.setState(state); + current_status.setLogMessage(logmessage); + current_status.setLocalizedResId(localizedResId); + current_status.setLevel(level); + current_status.setEipLevel(level); } @Override public void setConnectedVPN(String uuid) { } - private void updateStatus(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { - current_status = getInstance(); - current_status.setState(state); - current_status.setLogMessage(logmessage); - current_status.setLocalizedResId(localizedResId); - current_status.setLevel(level); - current_status.setChanged(); + + private void setEipLevel(ConnectionStatus level) { + EipLevel tmp = current_eip_level; + switch (level) { + case LEVEL_CONNECTED: + current_eip_level = EipLevel.CONNECTED; + break; + case LEVEL_VPNPAUSED: + throw new IllegalStateException("Ics-Openvpn's VPNPAUSED state is not supported by Bitmask"); + case LEVEL_CONNECTING_SERVER_REPLIED: + case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: + case LEVEL_WAITING_FOR_USER_INPUT: + case LEVEL_START: + current_eip_level = EipLevel.CONNECTING; + break; + case LEVEL_AUTH_FAILED: + case LEVEL_NOTCONNECTED: + current_eip_level = EipLevel.DISCONNECTED; + break; + case LEVEL_NONETWORK: + case LEVEL_BLOCKING: + setEipLevelWithDelay(level); + break; + case UNKNOWN_LEVEL: + current_eip_level = EipLevel.UNKNOWN; //?? + break; + } + if (tmp != current_eip_level) { + current_status.setChanged(); + current_status.notifyObservers(); + } } - public boolean wantsToDisconnect() { - return wants_to_disconnect; + @VisibleForTesting + EipLevel getEipLevel() { + return current_eip_level; } - public boolean isConnecting() { - return is_connecting; + /** + * This method intends to ignore states that are valid for less than a second. + * This way flickering UI changes can be avoided + * + * @param futureLevel + */ + private void setEipLevelWithDelay(ConnectionStatus futureLevel) { + new DelayTask(current_status.getLevel(), futureLevel).execute(); } - public boolean isConnected() { - return level == ConnectionStatus.LEVEL_CONNECTED; + private class DelayTask extends AsyncTask { + + private final ConnectionStatus currentLevel; + private final ConnectionStatus futureLevel; + + public DelayTask(ConnectionStatus currentLevel, ConnectionStatus futureLevel) { + this.currentLevel = currentLevel; + this.futureLevel = futureLevel; + } + protected Void doInBackground(Void... levels) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.interrupted(); + } + return null; + } + + protected void onPostExecute(Void result) {; + if (currentLevel == current_status.getLevel()) { + switch (futureLevel) { + case LEVEL_NONETWORK: + current_eip_level = EipLevel.DISCONNECTED; + break; + case LEVEL_BLOCKING: + current_eip_level = EipLevel.BLOCKING; + break; + default: + break; + } + current_status.setChanged(); + current_status.notifyObservers(); + } + } } - public boolean isDisconnected() { - return level == ConnectionStatus.LEVEL_NOTCONNECTED; + public boolean isConnecting() { + return current_eip_level == EipLevel.CONNECTING; } - public boolean isPaused() { - return level == ConnectionStatus.LEVEL_VPNPAUSED; + public boolean isConnected() { + return current_eip_level == EipLevel.CONNECTED; } - public void setConnecting() { - is_connecting = true; + /** + * @return true if current_eip_level is for at least a second {@link EipLevel#BLOCKING}. + * See {@link #setEipLevelWithDelay(ConnectionStatus)}. + */ + public boolean isBlocking() { + return current_eip_level == EipLevel.BLOCKING; + } - wants_to_disconnect = false; - current_status.setChanged(); - current_status.notifyObservers(); + /** + * + * @return true immediately after traffic blocking VoidVpn was established. + */ + public boolean isBlockingVpnEstablished() { + return vpn_level == ConnectionStatus.LEVEL_BLOCKING; } - public void setConnectedOrDisconnected() { - is_connecting = false; - wants_to_disconnect = false; - current_status.setChanged(); - current_status.notifyObservers(); + public boolean isDisconnected() { + return current_eip_level == EipLevel.DISCONNECTED; } - public void setDisconnecting() { - wants_to_disconnect = true; - is_connecting = false; + /** + * ics-openvpn's paused state is not implemented yet + * @return + */ + @Deprecated + public boolean isPaused() { + return vpn_level == ConnectionStatus.LEVEL_VPNPAUSED; } public String getState() { @@ -121,7 +212,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } public ConnectionStatus getLevel() { - return level; + return vpn_level; } private void setState(String state) { @@ -137,7 +228,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } private void setLevel(ConnectionStatus level) { - EipStatus.level = level; + this.vpn_level = level; } public boolean errorInLast(int lines, Context context) { @@ -169,7 +260,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { @Override public String toString() { - return "State: " + state + " Level: " + level.toString(); + return "State: " + state + " Level: " + vpn_level.toString(); } } 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 cbf0fed2..46c010ca 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -1,41 +1,58 @@ package se.leap.bitmaskclient.eip; -import android.content.*; -import android.net.*; -import android.os.*; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.IOException; + +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.R; + +import static se.leap.bitmaskclient.Dashboard.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.eip.Constants.ACTION_START_ALWAYS_ON_EIP; -import java.io.*; public class VoidVpnService extends VpnService { 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"; + private static final String STATE_STOP = "STOPVOIDVPN"; @Override - public int onStartCommand(Intent intent, int flags, int startId) { + public int onStartCommand(final Intent intent, int flags, int startId) { String action = intent != null ? intent.getAction() : ""; - if (action == Constants.START_BLOCKING_VPN_PROFILE) { + if (action.equals(Constants.START_BLOCKING_VPN_PROFILE)) { 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(Constants.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() { + establishBlockingVpn(); + SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + preferences.edit().putBoolean(Constants.IS_ALWAYS_ON, true).commit(); + requestVpnWithLastSelectedProfile(); + Log.d(TAG, "start blocking vpn profile - always on = true"); } }); thread.run(); } - return 0; + return START_STICKY; } @Override @@ -48,6 +65,8 @@ public class VoidVpnService extends VpnService { if (thread != null) thread.interrupt(); closeFd(); + VpnStatus.updateStateString(STATE_STOP, "", + R.string.void_vpn_stopped, ConnectionStatus.LEVEL_NOTCONNECTED); } public static boolean isRunning() throws NullPointerException { @@ -62,4 +81,45 @@ 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(ACTION_START_ALWAYS_ON_EIP); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + getApplicationContext().startForegroundService(startEIP); + } else { + getApplicationContext().startService(startEIP); + } + } + } -- cgit v1.2.3 From a60fa8124fc8c8cfc80ced0a8faa62abb39075f5 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 12 Dec 2017 15:05:30 +0100 Subject: #8742 add notifications for blocking vpn --- .../java/se/leap/bitmaskclient/eip/Constants.java | 3 +- .../java/se/leap/bitmaskclient/eip/EipStatus.java | 14 +- .../se/leap/bitmaskclient/eip/VoidVpnLauncher.java | 8 +- .../se/leap/bitmaskclient/eip/VoidVpnService.java | 157 +++++++++++++++++++-- 4 files changed, 160 insertions(+), 22 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/eip') diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java index ed4ebcbc..449c111d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Constants.java @@ -31,6 +31,8 @@ public interface Constants { public final static String ACTION_STOP_EIP = TAG + ".STOP_EIP"; public final static String ACTION_UPDATE_EIP_SERVICE = TAG + ".UPDATE_EIP_SERVICE"; public final static String ACTION_IS_EIP_RUNNING = TAG + ".IS_RUNNING"; + public final static String ACTION_START_BLOCKING_VPN = TAG + ".ACTION_START_BLOCKING_VPN"; + public final static String ACTION_STOP_BLOCKING_VPN = TAG + ".ACTION_STOP_BLOCKING_VPN"; public final static String EIP_NOTIFICATION = TAG + ".EIP_NOTIFICATION"; public final static String ALLOWED_ANON = "allow_anonymous"; public final static String ALLOWED_REGISTERED = "allow_registration"; @@ -39,7 +41,6 @@ public interface Constants { public final static String KEY = TAG + ".KEY"; public final static String RECEIVER_TAG = TAG + ".RECEIVER_TAG"; public final static String REQUEST_TAG = TAG + ".REQUEST_TAG"; - public final static String START_BLOCKING_VPN_PROFILE = TAG + ".START_BLOCKING_VPN_PROFILE"; public final static String PROVIDER_CONFIGURED = TAG + ".PROVIDER_CONFIGURED"; public final static String IS_ALWAYS_ON = TAG + ".IS_ALWAYS_ON"; public final static String RESTART_ON_BOOT = TAG + ".RESTART_ON_BOOT"; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index dc2e81f5..b1d7c994 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -66,12 +66,17 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { @Override public void updateState(final String state, final String logmessage, final int localizedResId, final ConnectionStatus level) { + ConnectionStatus tmp = current_status.getLevel(); current_status = getInstance(); current_status.setState(state); current_status.setLogMessage(logmessage); current_status.setLocalizedResId(localizedResId); current_status.setLevel(level); current_status.setEipLevel(level); + if (tmp != current_status.getLevel()) { + current_status.setChanged(); + current_status.notifyObservers(); + } } @Override @@ -80,7 +85,6 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { private void setEipLevel(ConnectionStatus level) { - EipLevel tmp = current_eip_level; switch (level) { case LEVEL_CONNECTED: current_eip_level = EipLevel.CONNECTED; @@ -105,10 +109,6 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { current_eip_level = EipLevel.UNKNOWN; //?? break; } - if (tmp != current_eip_level) { - current_status.setChanged(); - current_status.notifyObservers(); - } } @VisibleForTesting @@ -117,8 +117,8 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { } /** - * This method intends to ignore states that are valid for less than a second. - * This way flickering UI changes can be avoided + * This is a debouncing method ignoring states that are valid for less than a second. + * This way flickering UI changes can be avoided. * * @param futureLevel */ diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java index 5c9263b3..27e2d95e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -28,8 +28,12 @@ public class VoidVpnLauncher extends Activity { if (requestCode == VPN_USER_PERMISSION) { if (resultCode == RESULT_OK) { Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - void_vpn_service.setAction(Constants.START_BLOCKING_VPN_PROFILE); - startService(void_vpn_service); + void_vpn_service.setAction(Constants.ACTION_START_BLOCKING_VPN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(void_vpn_service); + } else { + startService(void_vpn_service); + } } } finish(); 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 46c010ca..e5e50f6e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -1,35 +1,61 @@ package se.leap.bitmaskclient.eip; +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.IOException; +import java.util.Observable; +import java.util.Observer; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.Dashboard; import se.leap.bitmaskclient.R; +import static android.os.Build.VERSION_CODES.O; import static se.leap.bitmaskclient.Dashboard.SHARED_PREFERENCES; import static se.leap.bitmaskclient.eip.Constants.ACTION_START_ALWAYS_ON_EIP; +import static se.leap.bitmaskclient.eip.Constants.ACTION_STOP_BLOCKING_VPN; -public class VoidVpnService extends VpnService { +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"; - private static final String STATE_STOP = "STOPVOIDVPN"; + public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "bitmask_void_vpn_news"; + private EipStatus eipStatus; + NotificationManager notificationManager; + NotificationManagerCompat compatNotificationManager; @Override - public int onStartCommand(final Intent intent, int flags, int startId) { + 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.equals(Constants.START_BLOCKING_VPN_PROFILE)) { + if (action.equals(Constants.ACTION_START_BLOCKING_VPN)) { thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); @@ -51,6 +77,8 @@ public class VoidVpnService extends VpnService { } }); thread.run(); + } else if (action.equals(ACTION_STOP_BLOCKING_VPN)) { + stop(); } return START_STICKY; } @@ -61,12 +89,28 @@ 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(); - VpnStatus.updateStateString(STATE_STOP, "", - R.string.void_vpn_stopped, ConnectionStatus.LEVEL_NOTCONNECTED); } public static boolean isRunning() throws NullPointerException { @@ -114,12 +158,101 @@ public class VoidVpnService extends VpnService { private void requestVpnWithLastSelectedProfile() { Intent startEIP = new Intent(getApplicationContext(), EIP.class); startEIP.setAction(ACTION_START_ALWAYS_ON_EIP); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - //noinspection NewApi - getApplicationContext().startForegroundService(startEIP); + 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 { - getApplicationContext().startService(startEIP); + 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(Constants.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; + } + } } -- cgit v1.2.3 From b84f9942a3f87f9d1e589221ed2eb3900518d8d9 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 14 Dec 2017 15:54:08 +0100 Subject: fix potential memory leak --- app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/src/main/java/se/leap/bitmaskclient/eip') diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java index b1d7c994..ddf152d2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -47,7 +47,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { * openvpn. */ private ConnectionStatus vpn_level = ConnectionStatus.LEVEL_NOTCONNECTED; - private EipLevel current_eip_level = EipLevel.DISCONNECTED; + private static EipLevel current_eip_level = EipLevel.DISCONNECTED; int last_error_line = 0; private String state, log_message; @@ -126,7 +126,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { new DelayTask(current_status.getLevel(), futureLevel).execute(); } - private class DelayTask extends AsyncTask { + private static class DelayTask extends AsyncTask { private final ConnectionStatus currentLevel; private final ConnectionStatus futureLevel; -- cgit v1.2.3