summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/eip
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/eip')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EIP.java73
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java175
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java19
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java239
4 files changed, 416 insertions, 90 deletions
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 0b35dc3d..a84ab941 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java
@@ -19,12 +19,25 @@ package se.leap.bitmaskclient.eip;
import android.app.*;
import android.content.*;
import android.os.*;
+import android.util.Log;
import org.json.*;
import de.blinkt.openvpn.*;
import se.leap.bitmaskclient.*;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_IS_RUNNING;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_ALWAYS_ON_EIP;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE;
+import static se.leap.bitmaskclient.Constants.EIP_RECEIVER;
+import static se.leap.bitmaskclient.Constants.EIP_REQUEST;
+import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
+
/**
* EIP is the abstract base class for interacting with and managing the Encrypted
* Internet Proxy connection. Connections are started, stopped, and queried through
@@ -55,9 +68,8 @@ public final class EIP extends IntentService {
@Override
public void onCreate() {
super.onCreate();
-
context = getApplicationContext();
- preferences = getSharedPreferences(Constants.SHARED_PREFERENCES, MODE_PRIVATE);
+ preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
eip_definition = eipDefinitionFromPreferences();
if (gateways_manager.isEmpty())
gatewaysFromPreferences();
@@ -66,17 +78,19 @@ public final class EIP extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
- mReceiver = intent.getParcelableExtra(Constants.EIP_RECEIVER);
+ mReceiver = intent.getParcelableExtra(EIP_RECEIVER);
- if (action.equals(Constants.EIP_ACTION_START))
+ if (action.equals(EIP_ACTION_START))
startEIP();
- else if (action.equals(Constants.EIP_ACTION_STOP))
+ else if (action.equals(EIP_ACTION_START_ALWAYS_ON_EIP))
+ startAlwaysOnEIP();
+ else if (action.equals(EIP_ACTION_STOP))
stopEIP();
- else if (action.equals(Constants.EIP_ACTION_IS_RUNNING))
+ else if (action.equals(EIP_ACTION_IS_RUNNING))
isRunning();
- else if (action.equals(Constants.EIP_ACTION_UPDATE))
+ else if (action.equals(EIP_ACTION_UPDATE))
updateEIPService();
- else if (action.equals(Constants.EIP_ACTION_CHECK_CERT_VALIDITY))
+ else if (action.equals(EIP_ACTION_CHECK_CERT_VALIDITY))
checkCertValidity();
}
@@ -88,15 +102,38 @@ public final class EIP extends IntentService {
private void startEIP() {
if (gateways_manager.isEmpty())
updateEIPService();
- earlyRoutes();
+ if (!EipStatus.getInstance().isBlockingVpnEstablished()) {
+ earlyRoutes();
+ }
gateway = gateways_manager.select();
if (gateway != null && gateway.getProfile() != null) {
mReceiver = VpnFragment.getReceiver();
launchActiveGateway();
- tellToReceiver(Constants.EIP_ACTION_START, Activity.RESULT_OK);
+ tellToReceiver(EIP_ACTION_START, Activity.RESULT_OK);
} else
- tellToReceiver(Constants.EIP_ACTION_START, Activity.RESULT_CANCELED);
+ tellToReceiver(EIP_ACTION_START, 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!");
+ }
}
/**
@@ -124,7 +161,7 @@ public final class EIP extends IntentService {
if (eip_status.isConnected() || eip_status.isConnecting())
result_code = Activity.RESULT_OK;
- tellToReceiver(Constants.EIP_ACTION_STOP, result_code);
+ tellToReceiver(EIP_ACTION_STOP, result_code);
}
/**
@@ -137,7 +174,7 @@ public final class EIP extends IntentService {
int resultCode = (eip_status.isConnected()) ?
Activity.RESULT_OK :
Activity.RESULT_CANCELED;
- tellToReceiver(Constants.EIP_ACTION_IS_RUNNING, resultCode);
+ tellToReceiver(EIP_ACTION_IS_RUNNING, resultCode);
}
/**
@@ -148,13 +185,13 @@ public final class EIP extends IntentService {
eip_definition = eipDefinitionFromPreferences();
if (eip_definition.length() > 0)
updateGateways();
- tellToReceiver(Constants.EIP_ACTION_UPDATE, Activity.RESULT_OK);
+ tellToReceiver(EIP_ACTION_UPDATE, Activity.RESULT_OK);
}
private JSONObject eipDefinitionFromPreferences() {
JSONObject result = new JSONObject();
try {
- String eip_definition_string = preferences.getString(Constants.PROVIDER_KEY, "");
+ String eip_definition_string = preferences.getString(PROVIDER_KEY, "");
if (!eip_definition_string.isEmpty()) {
result = new JSONObject(eip_definition_string);
}
@@ -184,17 +221,17 @@ public final class EIP extends IntentService {
}
private void checkCertValidity() {
- VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(Constants.PROVIDER_VPN_CERTIFICATE, ""));
+ VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, ""));
int resultCode = validator.isValid() ?
Activity.RESULT_OK :
Activity.RESULT_CANCELED;
- tellToReceiver(Constants.EIP_ACTION_CHECK_CERT_VALIDITY, resultCode);
+ tellToReceiver(EIP_ACTION_CHECK_CERT_VALIDITY, resultCode);
}
private void tellToReceiver(String action, int resultCode) {
if (mReceiver != null) {
Bundle resultData = new Bundle();
- resultData.putString(Constants.EIP_REQUEST, action);
+ resultData.putString(EIP_REQUEST, action);
mReceiver.send(resultCode, resultData);
}
}
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..ddf152d2 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 static 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();
+ 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
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) {
+ 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;
+ }
}
- public boolean wantsToDisconnect() {
- return wants_to_disconnect;
+ @VisibleForTesting
+ EipLevel getEipLevel() {
+ return current_eip_level;
}
- public boolean isConnecting() {
- return is_connecting;
+ /**
+ * 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
+ */
+ private void setEipLevelWithDelay(ConnectionStatus futureLevel) {
+ new DelayTask(current_status.getLevel(), futureLevel).execute();
}
- public boolean isConnected() {
- return level == ConnectionStatus.LEVEL_CONNECTED;
+ private static class DelayTask extends AsyncTask<Void, Void, Void> {
+
+ 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/VoidVpnLauncher.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
index b1aab79c..9a3c8f85 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java
@@ -1,11 +1,12 @@
package se.leap.bitmaskclient.eip;
-import android.app.*;
-import android.content.*;
-import android.net.*;
-import android.os.*;
+import android.app.Activity;
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.Build;
+import android.os.Bundle;
-import se.leap.bitmaskclient.Constants;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START_BLOCKING_VPN;
public class VoidVpnLauncher extends Activity {
@@ -30,8 +31,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.EIP_ACTION_BLOCK_VPN_PROFILE);
- startService(void_vpn_service);
+ void_vpn_service.setAction(EIP_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 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;
+ }
+ }
}