summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/se/leap')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java47
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Constants.java15
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Dashboard.java135
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java44
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/VpnFragment.java178
-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
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java11
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java3
11 files changed, 713 insertions, 226 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
index 953a559d..d7f574b2 100644
--- a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
+++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java
@@ -1,6 +1,16 @@
package se.leap.bitmaskclient;
+import android.annotation.TargetApi;
import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Build;
+
+import de.blinkt.openvpn.core.OpenVPNService;
+
+import static android.os.Build.VERSION_CODES.O;
/**
* Created by cyberta on 24.10.17.
@@ -13,5 +23,42 @@ public class BitmaskApp extends Application {
super.onCreate();
PRNGFixes.apply();
//TODO: add LeakCanary!
+ if (Build.VERSION.SDK_INT >= O)
+ createNotificationChannelsForOpenvpn();
}
+
+
+ @TargetApi(O)
+ private void createNotificationChannelsForOpenvpn() {
+ NotificationManager mNotificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ // Background message
+ CharSequence name = getString(R.string.channel_name_background);
+ NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID,
+ name, NotificationManager.IMPORTANCE_MIN);
+
+ mChannel.setDescription(getString(R.string.channel_description_background));
+ mChannel.enableLights(false);
+
+ mChannel.setLightColor(Color.DKGRAY);
+ mNotificationManager.createNotificationChannel(mChannel);
+
+ // Connection status change messages
+
+ name = getString(R.string.channel_name_status);
+ mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID,
+ name, NotificationManager.IMPORTANCE_DEFAULT);
+
+
+ mChannel.setDescription(getString(R.string.channel_description_status));
+ mChannel.enableLights(true);
+
+ mChannel.setLightColor(Color.BLUE);
+ mNotificationManager.createNotificationChannel(mChannel);
+
+ }
+
+
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java
index 8723191c..a7ab56fd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java
@@ -15,15 +15,20 @@ public interface Constants {
/////////////////////////////////////////////
String EIP_ACTION_CHECK_CERT_VALIDITY = "EIP.CHECK_CERT_VALIDITY";
- String EIP_ACTION_START = "EIP.START";
- String EIP_ACTION_STOP = "EIP.STOP";
- String EIP_ACTION_UPDATE = "EIP.UPDATE";
- String EIP_ACTION_IS_RUNNING = "EIP.IS_RUNNING";
- String EIP_ACTION_BLOCK_VPN_PROFILE = "EIP.ACTION_BLOCK_VPN_PROFILE";
+ String EIP_ACTION_START = "se.leap.bitmaskclient.EIP.START";
+ String EIP_ACTION_STOP = "se.leap.bitmaskclient.EIP.STOP";
+ String EIP_ACTION_UPDATE = "se.leap.bitmaskclient.EIP.UPDATE";
+ String EIP_ACTION_IS_RUNNING = "se.leap.bitmaskclient.EIP.IS_RUNNING";
+ String EIP_ACTION_START_ALWAYS_ON_EIP = "se.leap.bitmaskclient.START_ALWAYS_ON_EIP";
+ String EIP_ACTION_START_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_START_BLOCKING_VPN";
+ String EIP_ACTION_STOP_BLOCKING_VPN = "se.leap.bitmaskclient.EIP_ACTION_STOP_BLOCKING_VPN";
String EIP_NOTIFICATION = "EIP.NOTIFICATION";
String EIP_RECEIVER = "EIP.RECEIVER";
String EIP_REQUEST = "EIP.REQUEST";
+ String EIP_RESTART_ON_BOOT = "EIP.RESTART_ON_BOOT";
+ String EIP_IS_ALWAYS_ON = "EIP.EIP_IS_ALWAYS_ON";
+
//////////////////////////////////////////////
diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
index 1f0477c9..f1e7b3bd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java
@@ -45,6 +45,9 @@ import se.leap.bitmaskclient.userstatus.SessionDialog;
import se.leap.bitmaskclient.userstatus.User;
import se.leap.bitmaskclient.userstatus.UserStatusFragment;
+import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON;
+import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
+
/**
* The main user facing Activity of Bitmask Android, consisting of status, controls,
* and access to preferences.
@@ -59,14 +62,23 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
public static final String TAG = Dashboard.class.getSimpleName();
public static final String ACTION_QUIT = "quit";
+
+ /**
+ * When "Disconnect" is clicked from the notification this extra gets added to the calling intent.
+ */
public static final String ACTION_ASK_TO_CANCEL_VPN = "ask to cancel vpn";
+ /**
+ * if always-on feature is enabled, but there's no provider configured the EIP Service
+ * adds this intent extra. ACTION_CONFIGURE_ALWAYS_ON_PROFILE
+ * serves to start the Configuration Wizard on top of the Dashboard Activity.
+ */
+ public static final String ACTION_CONFIGURE_ALWAYS_ON_PROFILE = "configure always-on profile";
public static final String REQUEST_CODE = "request_code";
public static final String PARAMETERS = "dashboard parameters";
- public static final String START_ON_BOOT = "dashboard start on boot";
- //FIXME: remove OR FIX ON_BOOT
- public static final String ON_BOOT = "dashboard on boot";
+ public static final String APP_VERSION = "bitmask version";
- private static Context app;
+ //FIXME: context classes in static fields lead to memory leaks!
+ private static Context dashboardContext;
protected static SharedPreferences preferences;
private FragmentManagerEnhanced fragment_manager;
@@ -87,28 +99,18 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this);
- if (app == null) {
- app = this;
+ if (dashboardContext == null) {
+ dashboardContext = this;
handleVersion();
}
- boolean provider_exists = previousProviderExists(savedInstanceState);
- if (provider_exists) {
- provider = getProvider(savedInstanceState);
- if(!provider.isConfigured())
- startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
- else {
- buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false));
- user_status_fragment.restoreSessionStatus(savedInstanceState);
- }
- } else {
- startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
- }
+
+ prepareEIP(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
- handleVPNCancellation(getIntent());
+ handleVpnCancellation(getIntent());
}
private boolean previousProviderExists(Bundle savedInstanceState) {
@@ -172,7 +174,19 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
- handleVPNCancellation(intent);
+ handleIntentExtras(intent);
+ }
+
+ private void handleIntentExtras(Intent intent) {
+ if (intent.hasExtra(ACTION_ASK_TO_CANCEL_VPN)) {
+ handleVpnCancellation(intent);
+ } else if (intent.hasExtra(EIP_RESTART_ON_BOOT)) {
+ Log.d(TAG, "Dashboard: EIP_RESTART_ON_BOOT");
+ prepareEIP(null);
+ } else if (intent.hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) {
+ Log.d(TAG, "Dashboard: ACTION_CONFIGURE_ALWAYS_ON_PROFILE");
+ handleConfigureAlwaysOn(getIntent());
+ }
}
@Override
@@ -195,15 +209,44 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
}
}
- private void handleVPNCancellation(Intent intent) {
+ private void handleVpnCancellation(Intent intent) {
if (intent.hasExtra(Dashboard.ACTION_ASK_TO_CANCEL_VPN)) {
eip_fragment.askToStopEIP();
intent.removeExtra(ACTION_ASK_TO_CANCEL_VPN);
}
}
+ private void handleConfigureAlwaysOn(Intent intent) {
+ intent.removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE);
+ Log.d(TAG, "start Configuration wizard!");
+ startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
+ }
+
+ private void prepareEIP(Bundle savedInstanceState) {
+ boolean provider_exists = previousProviderExists(savedInstanceState);
+ if (provider_exists) {
+ provider = getProvider(savedInstanceState);
+ if(!provider.isConfigured()) {
+ configureLeapProvider();
+ } else {
+ Log.d(TAG, "vpn provider is configured");
+ buildDashboard(getIntent().getBooleanExtra(EIP_RESTART_ON_BOOT, false));
+ user_status_fragment.restoreSessionStatus(savedInstanceState);
+ }
+ } else {
+ configureLeapProvider();
+ }
+ }
+
+ private void configureLeapProvider() {
+ if (getIntent().hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) {
+ getIntent().removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE);
+ }
+ startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
+ }
@SuppressLint("CommitPrefEdits")
private void providerToPreferences(Provider provider) {
+ //FIXME: figure out why .commit() is used and try refactor that cause, currently runs on UI thread
preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit();
preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply();
preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply();
@@ -235,7 +278,10 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
* Inflates permanent UI elements of the View and contains logic for what
* service dependent UI elements to include.
*/
- private void buildDashboard(boolean hide_and_turn_on_eip) {
+ //TODO: REFACTOR ME! Consider implementing a manager that handles most of VpnFragment's logic about handling EIP commands.
+ //This way, we could avoid to create UI elements (like fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG); )
+ // just to start services and destroy them afterwards
+ private void buildDashboard(boolean hideAndTurnOnEipOnBoot) {
setContentView(R.layout.dashboard);
ButterKnife.inject(this);
@@ -249,24 +295,43 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
if (provider.hasEIP()) {
fragment_manager.removePreviousFragment(VpnFragment.TAG);
- eip_fragment = new VpnFragment();
-
- if (hide_and_turn_on_eip) {
- //TODO: remove line below if not in use anymore...
- preferences.edit().remove(Dashboard.START_ON_BOOT).apply();
- //FIXME: always start on Boot? Why do we keep shared preferences then?
- Bundle arguments = new Bundle();
- arguments.putBoolean(VpnFragment.START_ON_BOOT, true);
- if (eip_fragment != null) eip_fragment.setArguments(arguments);
- }
-
+ eip_fragment = prepareEipFragment(hideAndTurnOnEipOnBoot);
fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG);
- if (hide_and_turn_on_eip) {
+ if (hideAndTurnOnEipOnBoot) {
onBackPressed();
}
}
}
+ /**
+ *
+ * @param hideAndTurnOnEipOnBoot Flag that indicates if system intent android.intent.action.BOOT_COMPLETED
+ * has caused to start Dashboard
+ * @return
+ */
+ private VpnFragment prepareEipFragment(boolean hideAndTurnOnEipOnBoot) {
+ VpnFragment eip_fragment = new VpnFragment();
+
+ if (hideAndTurnOnEipOnBoot && !isAlwaysOn()) {
+ preferences.edit().remove(EIP_RESTART_ON_BOOT).apply();
+ Bundle arguments = new Bundle();
+ arguments.putBoolean(VpnFragment.START_EIP_ON_BOOT, true);
+ Log.d(TAG, "set START_EIP_ON_BOOT argument for eip_fragment");
+ eip_fragment.setArguments(arguments);
+
+ }
+ return eip_fragment;
+ }
+
+ /**
+ * checks if Android's VPN feature 'always-on' is enabled for Bitmask
+ * @return
+ */
+ private boolean isAlwaysOn() {
+ return preferences.getBoolean(EIP_IS_ALWAYS_ON, false);
+ }
+
+
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (provider.allowsRegistration()) {
@@ -369,7 +434,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec
}
public static Context getContext() {
- return app;
+ return dashboardContext;
}
public static Provider getProvider() { return provider; }
diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
index 9394e1b1..f9aa2660 100644
--- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
+++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java
@@ -1,7 +1,15 @@
package se.leap.bitmaskclient;
-import android.content.*;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON;
+import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
public class OnBootReceiver extends BroadcastReceiver {
@@ -10,15 +18,33 @@ public class OnBootReceiver extends BroadcastReceiver {
// Debug: am broadcast -a android.intent.action.BOOT_COMPLETED
@Override
public void onReceive(Context context, Intent intent) {
+ //Lint complains if we're not checking the intent action
+ if (intent == null || !ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ return;
+ }
preferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES, Context.MODE_PRIVATE);
- boolean provider_configured = !preferences.getString(Provider.KEY, "").isEmpty();
- boolean start_on_boot = preferences.getBoolean(Dashboard.START_ON_BOOT, false);
- if (provider_configured && start_on_boot) {
- Intent dashboard_intent = new Intent(context, Dashboard.class);
- dashboard_intent.setAction(Constants.EIP_ACTION_START);
- dashboard_intent.putExtra(Dashboard.ON_BOOT, true);
- dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(dashboard_intent);
+ boolean providerConfigured = !preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty();
+ boolean startOnBoot = preferences.getBoolean(EIP_RESTART_ON_BOOT, false);
+ boolean isAlwaysOnConfigured = preferences.getBoolean(EIP_IS_ALWAYS_ON, false);
+ Log.d("OpenVPN", "OpenVPN onBoot intent received. Provider configured? " + providerConfigured + " Start on boot? " + startOnBoot + " isAlwaysOn feature configured: " + isAlwaysOnConfigured);
+ if (providerConfigured) {
+ if (isAlwaysOnConfigured) {
+ //exit because the app is already setting up the vpn
+ return;
+ }
+ if (startOnBoot) {
+ Intent dashboard_intent = new Intent(context, Dashboard.class);
+ dashboard_intent.putExtra(EIP_RESTART_ON_BOOT, true);
+ dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(dashboard_intent);
+ }
+ } else {
+ if (isAlwaysOnConfigured) {
+ Intent dashboard_intent = new Intent(context, Dashboard.class);
+ dashboard_intent.putExtra(Dashboard.ACTION_CONFIGURE_ALWAYS_ON_PROFILE, true);
+ dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(dashboard_intent);
+ }
}
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
index f787a955..f1a15efd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java
@@ -16,26 +16,53 @@
*/
package se.leap.bitmaskclient;
-import android.app.*;
-import android.content.*;
-import android.os.*;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.util.Log;
-import android.view.*;
-import android.widget.*;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
-import org.jetbrains.annotations.*;
+import java.util.Observable;
+import java.util.Observer;
-import java.util.*;
-
-import butterknife.*;
+import butterknife.ButterKnife;
+import butterknife.InjectView;
+import butterknife.OnClick;
import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
import de.blinkt.openvpn.core.OpenVPNService;
import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.core.VpnStatus;
+import mbanje.kurt.fabbutton.FabButton;
import se.leap.bitmaskclient.eip.EIP;
import se.leap.bitmaskclient.eip.EipStatus;
import se.leap.bitmaskclient.eip.VoidVpnService;
-import se.leap.bitmaskclient.userstatus.FabButton;
+
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_START;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_STOP_BLOCKING_VPN;
+import static se.leap.bitmaskclient.Constants.EIP_ACTION_UPDATE;
+import static se.leap.bitmaskclient.Constants.EIP_NOTIFICATION;
+import static se.leap.bitmaskclient.Constants.EIP_RECEIVER;
+import static se.leap.bitmaskclient.Constants.EIP_REQUEST;
+import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOWED_REGISTERED;
+import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS;
+import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
public class VpnFragment extends Fragment implements Observer {
@@ -43,14 +70,14 @@ public class VpnFragment extends Fragment implements Observer {
public static final String IS_PENDING = TAG + ".is_pending";
protected static final String IS_CONNECTED = TAG + ".is_connected";
- public static final String START_ON_BOOT = "start on boot";
+ public static final String START_EIP_ON_BOOT = "start on boot";
@InjectView(R.id.vpn_status_image)
FabButton vpn_status_image;
@InjectView(R.id.vpn_main_button)
Button main_button;
- private static Dashboard dashboard;
+ private Dashboard dashboard;
private static EIPReceiver eip_receiver;
private static EipStatus eip_status;
private boolean wants_to_connect;
@@ -96,26 +123,19 @@ public class VpnFragment extends Fragment implements Observer {
ButterKnife.inject(this, view);
Bundle arguments = getArguments();
- if (arguments != null && arguments.containsKey(START_ON_BOOT) && arguments.getBoolean(START_ON_BOOT))
+ if (arguments != null && arguments.containsKey(START_EIP_ON_BOOT) && arguments.getBoolean(START_EIP_ON_BOOT)) {
startEipFromScratch();
- if (savedInstanceState != null) restoreState(savedInstanceState);
+ }
return view;
}
- private void restoreState(@NotNull Bundle savedInstanceState) {
- if (savedInstanceState.getBoolean(IS_PENDING))
- eip_status.setConnecting();
- else if (savedInstanceState.getBoolean(IS_CONNECTED))
- eip_status.setConnectedOrDisconnected();
- }
-
@Override
public void onResume() {
super.onResume();
//FIXME: avoid race conditions while checking certificate an logging in at about the same time
//eipCommand(Constants.EIP_ACTION_CHECK_CERT_VALIDITY);
- handleNewState(eip_status);
+ handleNewState();
bindOpenVpnService();
}
@@ -127,14 +147,12 @@ public class VpnFragment extends Fragment implements Observer {
@Override
public void onSaveInstanceState(Bundle outState) {
- outState.putBoolean(IS_PENDING, eip_status.isConnecting());
outState.putBoolean(IS_CONNECTED, eip_status.isConnected());
super.onSaveInstanceState(outState);
}
- private void saveStatus() {
- boolean is_on = eip_status.isConnected() || eip_status.isConnecting();
- Dashboard.preferences.edit().putBoolean(Dashboard.START_ON_BOOT, is_on).commit();
+ private void saveStatus(boolean restartOnBoot) {
+ Dashboard.preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply();
}
@OnClick(R.id.vpn_main_button)
@@ -143,8 +161,6 @@ public class VpnFragment extends Fragment implements Observer {
handleSwitchOff();
else
handleSwitchOn();
-
- saveStatus();
}
private void handleSwitchOn() {
@@ -153,7 +169,6 @@ public class VpnFragment extends Fragment implements Observer {
else if (canLogInToStartEIP()) {
wants_to_connect = true;
Bundle bundle = new Bundle();
- bundle.putBoolean(IS_PENDING, true);
dashboard.sessionDialog(bundle);
} else {
Log.d(TAG, "WHAT IS GOING ON HERE?!");
@@ -163,13 +178,13 @@ public class VpnFragment extends Fragment implements Observer {
}
private boolean canStartEIP() {
- boolean certificateExists = !Dashboard.preferences.getString(Constants.PROVIDER_VPN_CERTIFICATE, "").isEmpty();
- boolean isAllowedAnon = Dashboard.preferences.getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, false);
+ boolean certificateExists = !Dashboard.preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty();
+ boolean isAllowedAnon = Dashboard.preferences.getBoolean(PROVIDER_ALLOW_ANONYMOUS, false);
return (isAllowedAnon || certificateExists) && !eip_status.isConnected() && !eip_status.isConnecting();
}
private boolean canLogInToStartEIP() {
- boolean isAllowedRegistered = Dashboard.preferences.getBoolean(Constants.PROVIDER_ALLOWED_REGISTERED, false);
+ boolean isAllowedRegistered = Dashboard.preferences.getBoolean(PROVIDER_ALLOWED_REGISTERED, false);
boolean isLoggedIn = !LeapSRPSession.getToken().isEmpty();
return isAllowedRegistered && !isLoggedIn && !eip_status.isConnecting() && !eip_status.isConnected();
}
@@ -179,8 +194,9 @@ public class VpnFragment extends Fragment implements Observer {
askPendingStartCancellation();
} else if (eip_status.isConnected()) {
askToStopEIP();
- } else
+ } else {
updateIcon();
+ }
}
private void askPendingStartCancellation() {
@@ -203,25 +219,30 @@ public class VpnFragment extends Fragment implements Observer {
public void startEipFromScratch() {
wants_to_connect = false;
- eip_status.setConnecting();
-
- saveStatus();
- eipCommand(Constants.EIP_ACTION_START);
+ saveStatus(true);
+ eipCommand(EIP_ACTION_START);
}
private void stop() {
- if (eip_status.isConnecting())
- VoidVpnService.stop();
+ saveStatus(false);
+ if (eip_status.isBlockingVpnEstablished()) {
+ stopBlockingVpn();
+ }
disconnect();
}
+ private void stopBlockingVpn() {
+ Log.d(TAG, "stop VoidVpn!");
+ Intent stopVoidVpnIntent = new Intent(dashboard, VoidVpnService.class);
+ stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN);
+ dashboard.startService(stopVoidVpnIntent);
+ }
+
private void disconnect() {
- eip_status.setDisconnecting();
ProfileManager.setConntectedVpnProfileDisconnected(dashboard);
if (mService != null) {
try {
mService.stopVPN(false);
- eip_status.setConnectedOrDisconnected();
} catch (RemoteException e) {
VpnStatus.logException(e);
}
@@ -229,7 +250,8 @@ public class VpnFragment extends Fragment implements Observer {
}
protected void stopEipIfPossible() {
- eipCommand(Constants.EIP_ACTION_STOP);
+ //FIXME: no need to start a service here!
+ eipCommand(EIP_ACTION_STOP);
}
private void downloadEIPServiceConfig() {
@@ -257,7 +279,7 @@ public class VpnFragment extends Fragment implements Observer {
}
protected void updateEipService() {
- eipCommand(Constants.EIP_ACTION_UPDATE);
+ eipCommand(EIP_ACTION_UPDATE);
}
/**
@@ -270,7 +292,7 @@ public class VpnFragment extends Fragment implements Observer {
// TODO validate "action"...how do we get the list of intent-filters for a class via Android API?
Intent vpn_intent = new Intent(dashboard.getApplicationContext(), EIP.class);
vpn_intent.setAction(action);
- vpn_intent.putExtra(Constants.EIP_RECEIVER, eip_receiver);
+ vpn_intent.putExtra(EIP_RECEIVER, eip_receiver);
dashboard.startService(vpn_intent);
}
@@ -278,36 +300,33 @@ public class VpnFragment extends Fragment implements Observer {
public void update(Observable observable, Object data) {
if (observable instanceof EipStatus) {
eip_status = (EipStatus) observable;
- final EipStatus eip_status = (EipStatus) observable;
dashboard.runOnUiThread(new Runnable() {
@Override
public void run() {
- handleNewState(eip_status);
+ handleNewState();
}
});
}
}
- private void handleNewState(EipStatus eip_status) {
- Context context = dashboard.getApplicationContext();
- String error = eip_status.lastError(5, context);
-
- if (!error.isEmpty()) VoidVpnService.stop();
+ private void handleNewState() {
updateIcon();
updateButton();
}
private void updateIcon() {
- if (eip_status.isConnected() || eip_status.isConnecting()) {
- if(eip_status.isConnecting()) {
- vpn_status_image.showProgress(true);
- vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo);
- vpn_status_image.setTag(R.drawable.ic_stat_vpn_empty_halo);
- } else {
- vpn_status_image.showProgress(false);
- vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn);
- vpn_status_image.setTag(R.drawable.ic_stat_vpn);
- }
+ if (eip_status.isBlocking()) {
+ vpn_status_image.showProgress(false);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn_blocking, R.drawable.ic_stat_vpn_blocking);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn_blocking);
+ } else if (eip_status.isConnecting()) {
+ vpn_status_image.showProgress(true);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn_empty_halo);
+ } else if (eip_status.isConnected()){
+ vpn_status_image.showProgress(false);
+ vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn);
+ vpn_status_image.setTag(R.drawable.ic_stat_vpn);
} else {
vpn_status_image.setIcon(R.drawable.ic_stat_vpn_offline, R.drawable.ic_stat_vpn_offline);
vpn_status_image.setTag(R.drawable.ic_stat_vpn_offline);
@@ -316,17 +335,28 @@ public class VpnFragment extends Fragment implements Observer {
}
private void updateButton() {
- if (eip_status.isConnected() || eip_status.isConnecting()) {
- if(eip_status.isConnecting()) {
- main_button.setText(dashboard.getString(android.R.string.cancel));
- } else {
- main_button.setText(dashboard.getString(R.string.vpn_button_turn_off));
- }
+ if (eip_status.isConnecting()) {
+ main_button.setText(dashboard.getString(android.R.string.cancel));
+ } else if (eip_status.isConnected() || isOpenVpnRunningWithoutNetwork()) {
+ main_button.setText(dashboard.getString(R.string.vpn_button_turn_off));
} else {
main_button.setText(dashboard.getString(R.string.vpn_button_turn_on));
}
}
+ private boolean isOpenVpnRunningWithoutNetwork() {
+ boolean isRunning = false;
+ try {
+ isRunning = eip_status.getLevel() == LEVEL_NONETWORK &&
+ mService.isVpnRunning();
+ } catch (Exception e) {
+ //eat me
+ e.printStackTrace();
+ }
+
+ return isRunning;
+ }
+
private void bindOpenVpnService() {
Intent intent = new Intent(dashboard, OpenVPNService.class);
intent.setAction(OpenVPNService.START_SERVICE);
@@ -343,9 +373,9 @@ public class VpnFragment extends Fragment implements Observer {
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
- String request = resultData.getString(Constants.EIP_REQUEST);
+ String request = resultData.getString(EIP_REQUEST);
- if (request.equals(Constants.EIP_ACTION_START)) {
+ if (request.equals(EIP_ACTION_START)) {
switch (resultCode) {
case Activity.RESULT_OK:
break;
@@ -353,7 +383,7 @@ public class VpnFragment extends Fragment implements Observer {
break;
}
- } else if (request.equals(Constants.EIP_ACTION_STOP)) {
+ } else if (request.equals(EIP_ACTION_STOP)) {
switch (resultCode) {
case Activity.RESULT_OK:
stop();
@@ -361,14 +391,14 @@ public class VpnFragment extends Fragment implements Observer {
case Activity.RESULT_CANCELED:
break;
}
- } else if (request.equals(Constants.EIP_NOTIFICATION)) {
+ } else if (request.equals(EIP_NOTIFICATION)) {
switch (resultCode) {
case Activity.RESULT_OK:
break;
case Activity.RESULT_CANCELED:
break;
}
- } else if (request.equals(Constants.EIP_ACTION_CHECK_CERT_VALIDITY)) {
+ } else if (request.equals(EIP_ACTION_CHECK_CERT_VALIDITY)) {
switch (resultCode) {
case Activity.RESULT_OK:
break;
@@ -376,14 +406,14 @@ public class VpnFragment extends Fragment implements Observer {
dashboard.downloadVpnCertificate();
break;
}
- } else if (request.equals(Constants.EIP_ACTION_UPDATE)) {
+ } else if (request.equals(EIP_ACTION_UPDATE)) {
switch (resultCode) {
case Activity.RESULT_OK:
if (wants_to_connect)
startEipFromScratch();
break;
case Activity.RESULT_CANCELED:
- handleNewState(eip_status);
+ handleNewState();
break;
}
}
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;
+ }
+ }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
index d124c395..61349490 100644
--- a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java
@@ -60,8 +60,6 @@ public class SessionDialog extends DialogFragment {
@InjectView(R.id.password_entered)
EditText password_field;
- private static boolean is_eip_pending = false;
-
public static SessionDialog getInstance(Provider provider, Bundle arguments) {
SessionDialog dialog = new SessionDialog();
if (provider.getName().equalsIgnoreCase("riseup")) {
@@ -100,7 +98,6 @@ public class SessionDialog extends DialogFragment {
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
- interface_with_Dashboard.cancelLoginOrSignup();
}
})
.setNeutralButton(R.string.signup_button, new DialogInterface.OnClickListener() {
@@ -116,7 +113,6 @@ public class SessionDialog extends DialogFragment {
}
private void setUp(Bundle arguments) {
- is_eip_pending = arguments.getBoolean(VpnFragment.IS_PENDING, false);
if (arguments.containsKey(ERRORS.PASSWORD_INVALID_LENGTH.toString()))
password_field.setError(getString(R.string.error_not_valid_password_user_message));
else if (arguments.containsKey(ERRORS.RISEUP_WARNING.toString())) {
@@ -160,7 +156,6 @@ public class SessionDialog extends DialogFragment {
public void signUp(String username, String password);
- public void cancelLoginOrSignup();
}
SessionDialogInterface interface_with_Dashboard;
@@ -177,10 +172,4 @@ public class SessionDialog extends DialogFragment {
}
}
- @Override
- public void onCancel(DialogInterface dialog) {
- super.onCancel(dialog);
- if (is_eip_pending)
- interface_with_Dashboard.cancelLoginOrSignup();
- }
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
index 20189904..14323f8e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java
@@ -24,7 +24,6 @@ import se.leap.bitmaskclient.ProviderAPI;
import se.leap.bitmaskclient.ProviderAPICommand;
import se.leap.bitmaskclient.ProviderAPIResultReceiver;
import se.leap.bitmaskclient.R;
-import se.leap.bitmaskclient.eip.EipStatus;
public class UserStatusFragment extends Fragment implements Observer, SessionDialog.SessionDialogInterface {
@@ -169,7 +168,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia
}
public void cancelLoginOrSignup() {
- EipStatus.getInstance().setConnectedOrDisconnected();
+ //EipStatus.getInstance().setConnectedOrDisconnected();
}
private Bundle bundlePassword(String password) {