From ee1fe71f67d017af2d1695e438b28dea4a38cb38 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 5 Dec 2017 00:41:07 +0100 Subject: #8742 add some icons that represent the blocking vpn state --- app/src/main/res/drawable-hdpi/ic_stat_vpn_blocking.png | Bin 0 -> 1427 bytes app/src/main/res/drawable-ldpi/ic_stat_vpn_blocking.png | Bin 0 -> 514 bytes app/src/main/res/drawable-mdpi/ic_stat_vpn_blocking.png | Bin 0 -> 514 bytes app/src/main/res/drawable-xhdpi/ic_stat_vpn_blocking.png | Bin 0 -> 1427 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_vpn_blocking.png create mode 100644 app/src/main/res/drawable-ldpi/ic_stat_vpn_blocking.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_vpn_blocking.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_vpn_blocking.png diff --git a/app/src/main/res/drawable-hdpi/ic_stat_vpn_blocking.png b/app/src/main/res/drawable-hdpi/ic_stat_vpn_blocking.png new file mode 100644 index 00000000..22ea5603 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_vpn_blocking.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_stat_vpn_blocking.png b/app/src/main/res/drawable-ldpi/ic_stat_vpn_blocking.png new file mode 100644 index 00000000..bbf8e5fd Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_stat_vpn_blocking.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_vpn_blocking.png b/app/src/main/res/drawable-mdpi/ic_stat_vpn_blocking.png new file mode 100644 index 00000000..bbf8e5fd Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_vpn_blocking.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_vpn_blocking.png b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_blocking.png new file mode 100644 index 00000000..a6840a41 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_vpn_blocking.png differ -- cgit v1.2.3 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 --- .../openvpn/core/IOpenVPNServiceInternal.aidl | 2 + .../de/blinkt/openvpn/core/ConnectionStatus.java | 1 + .../de/blinkt/openvpn/core/OpenVPNService.java | 20 +++ .../main/java/se/leap/bitmaskclient/Dashboard.java | 134 +++++++++++----- .../java/se/leap/bitmaskclient/OnBootReceiver.java | 43 +++-- .../java/se/leap/bitmaskclient/VpnFragment.java | 142 ++++++++++------- .../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 +++++++++--- .../bitmaskclient/userstatus/SessionDialog.java | 2 +- .../userstatus/UserStatusFragment.java | 3 +- app/src/main/res/values/strings.xml | 5 + 13 files changed, 492 insertions(+), 168 deletions(-) diff --git a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl index 3958bcf3..b19cf99e 100644 --- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl +++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl @@ -20,4 +20,6 @@ interface IOpenVPNServiceInternal { * @return true if there was a process that has been send a stop signal */ boolean stopVPN(boolean replaceConnection); + + boolean isVpnRunning(); } diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java index 03d842e3..3e6d23f7 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/ConnectionStatus.java @@ -21,6 +21,7 @@ public enum ConnectionStatus implements Parcelable { LEVEL_START, LEVEL_AUTH_FAILED, LEVEL_WAITING_FOR_USER_INPUT, + LEVEL_BLOCKING, // used for Bitmask's VoidVPN UNKNOWN_LEVEL; @Override diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index ded7490a..86e1bb02 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -115,6 +115,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public boolean stopVPN(boolean replaceConnection) throws RemoteException { return OpenVPNService.this.stopVPN(replaceConnection); } + + @Override + public boolean isVpnRunning() throws RemoteException { + return OpenVPNService.this.isVpnRunning(); + } }; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java @@ -412,6 +417,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } + @Override public void userPause(boolean shouldBePaused) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(shouldBePaused); @@ -425,6 +431,20 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac return false; } + /** + * used in Bitmask + */ + @Override + public boolean isVpnRunning() { + boolean hasVPNProcessThread = false; + synchronized (mProcessLock) { + hasVPNProcessThread = mProcessThread != null && mProcessThread.isAlive(); + } + + return hasVPNProcessThread; + + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 3f1663d0..1a4adc1d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -63,6 +63,9 @@ import se.leap.bitmaskclient.userstatus.SessionDialog; import se.leap.bitmaskclient.userstatus.User; import se.leap.bitmaskclient.userstatus.UserStatusFragment; +import static se.leap.bitmaskclient.eip.Constants.IS_ALWAYS_ON; +import static se.leap.bitmaskclient.eip.Constants.RESTART_ON_BOOT; + /** * The main user facing Activity of Bitmask Android, consisting of status, controls, * and access to preferences. @@ -78,15 +81,23 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec public static final String TAG = Dashboard.class.getSimpleName(); public static final String SHARED_PREFERENCES = "LEAPPreferences"; 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; @@ -108,31 +119,21 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec ProviderAPICommand.initialize(this); providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this); - if (app == null) { - app = this; + if (dashboardContext == null) { + dashboardContext = this; VpnStatus.initLogCache(getApplicationContext().getCacheDir()); handleVersion(); User.init(getString(R.string.default_username)); } - 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) { @@ -198,7 +199,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(RESTART_ON_BOOT)) { + Log.d(TAG, "Dashboard: 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 @@ -221,15 +234,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(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(); @@ -261,7 +303,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); @@ -275,24 +320,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(Constants.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(IS_ALWAYS_ON, false); + } + + @Override public boolean onPrepareOptionsMenu(Menu menu) { if (provider.allowsRegistration()) { @@ -395,7 +459,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 9171e816..943e877a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -1,25 +1,46 @@ 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 se.leap.bitmaskclient.eip.*; +import static se.leap.bitmaskclient.eip.Constants.IS_ALWAYS_ON; +import static se.leap.bitmaskclient.eip.Constants.RESTART_ON_BOOT; +import static se.leap.bitmaskclient.eip.Constants.VPN_CERTIFICATE; public class OnBootReceiver extends BroadcastReceiver { SharedPreferences preferences; - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED + + // Debug: su && am broadcast -a android.intent.action.BOOT_COMPLETED @Override public void onReceive(Context context, Intent intent) { preferences = context.getSharedPreferences(Dashboard.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.ACTION_START_EIP); - dashboard_intent.putExtra(Dashboard.ON_BOOT, true); - dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(dashboard_intent); + boolean provider_configured = !preferences.getString(VPN_CERTIFICATE, "").isEmpty(); + boolean start_on_boot = preferences.getBoolean(RESTART_ON_BOOT, false); + boolean isAlwaysOnConfigured = preferences.getBoolean(IS_ALWAYS_ON, false); + Log.d("OpenVPN", "OpenVPN onBoot intent received. Provider configured? " + provider_configured + " Start on boot? " + start_on_boot + " isAlwaysOn feature configured: " + isAlwaysOnConfigured); + if (provider_configured) { + if (isAlwaysOnConfigured) { + //exit because the app is already setting up the vpn + return; + } + if (start_on_boot) { + Intent dashboard_intent = new Intent(context, Dashboard.class); + dashboard_intent.putExtra(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 c85b0151..57c066aa 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -16,24 +16,50 @@ */ 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 org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.Observable; +import java.util.Observer; -import butterknife.*; +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; +import de.blinkt.openvpn.core.ConnectionStatus; 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.*; -import se.leap.bitmaskclient.eip.*; +import mbanje.kurt.fabbutton.FabButton; +import se.leap.bitmaskclient.eip.Constants; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipStatus; +import se.leap.bitmaskclient.eip.VoidVpnService; + +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.BLOCKING; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTED; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTING; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTED; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTING; public class VpnFragment extends Fragment implements Observer { @@ -41,7 +67,7 @@ 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; @@ -94,26 +120,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.ACTION_CHECK_CERT_VALIDITY); - handleNewState(eip_status); + handleNewState(); bindOpenVpnService(); } @@ -130,9 +149,9 @@ public class VpnFragment extends Fragment implements Observer { 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) { + //boolean is_on = eip_status.isConnected() || eip_status.isConnecting() || eip_status.isBlocking(); + Dashboard.preferences.edit().putBoolean(Constants.RESTART_ON_BOOT, restartOnBoot).commit(); } @OnClick(R.id.vpn_main_button) @@ -142,7 +161,7 @@ public class VpnFragment extends Fragment implements Observer { else handleSwitchOn(); - saveStatus(); + saveStatus(eip_status.isConnected() || eip_status.isConnecting()); } private void handleSwitchOn() { @@ -177,8 +196,11 @@ public class VpnFragment extends Fragment implements Observer { askPendingStartCancellation(); } else if (eip_status.isConnected()) { askToStopEIP(); - } else + } else if (eip_status.isBlocking()) { + stop(); + } else { updateIcon(); + } } private void askPendingStartCancellation() { @@ -201,25 +223,26 @@ public class VpnFragment extends Fragment implements Observer { public void startEipFromScratch() { wants_to_connect = false; - eip_status.setConnecting(); + //eip_status.setEipLevel(BLOCKING); - saveStatus(); + saveStatus(true); eipCommand(Constants.ACTION_START_EIP); } private void stop() { - if (eip_status.isConnecting()) + + if (eip_status.isBlockingVpnEstablished()) { + Log.d(TAG, "stop VoidVpn!"); VoidVpnService.stop(); + } disconnect(); } 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); } @@ -227,6 +250,7 @@ public class VpnFragment extends Fragment implements Observer { } protected void stopEipIfPossible() { + //FIXME: no need to start a service here! eipCommand(Constants.ACTION_STOP_EIP); } @@ -276,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); @@ -314,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); @@ -381,7 +413,7 @@ public class VpnFragment extends Fragment implements Observer { startEipFromScratch(); break; case Activity.RESULT_CANCELED: - handleNewState(eip_status); + handleNewState(); break; } } 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); + } + } + } 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..37744927 100644 --- a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java @@ -159,7 +159,7 @@ public class SessionDialog extends DialogFragment { public void logIn(String username, String password); public void signUp(String username, String password); - + //FIXME: can we remove this method? public void 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) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04e83a18..70f6f4ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,5 +76,10 @@ is being registered. Turn on Turn off + Stop blocking Bitmask Log + Bitmask blocks all outgoing internet traffic. + Failed to establish blocking VPN. + Stopped blocking all outgoing internet traffic. + Blocking traffic -- cgit v1.2.3 From 95aded1343009206eeb267ff2875e6060774ed22 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 7 Dec 2017 13:24:41 +0100 Subject: #8742 add changes for ics-openvpn --- ics-openvpn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ics-openvpn b/ics-openvpn index 95403c6c..f2edf86a 160000 --- a/ics-openvpn +++ b/ics-openvpn @@ -1 +1 @@ -Subproject commit 95403c6cb19b2f08df187eeb5bf67030d58bb87f +Subproject commit f2edf86aeb4ca90ad7f2e9589fa2ffa1f477dfc7 -- cgit v1.2.3 From 2852e3c1ada994b3ca2aff4a19e98c16ad540a25 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 7 Dec 2017 13:35:36 +0100 Subject: add some tests for EipStatus --- .../test/TestDashboardIntegration.java | 2 +- .../test/UserStatusTestController.java | 2 +- .../se/leap/bitmaskclient/eip/EipStatusTest.java | 96 ++++++++++++++++++++++ 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 app/src/test/java/se/leap/bitmaskclient/eip/EipStatusTest.java diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/TestDashboardIntegration.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/TestDashboardIntegration.java index faeb1791..ad2b4a00 100644 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/TestDashboardIntegration.java +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/TestDashboardIntegration.java @@ -2,7 +2,7 @@ package se.leap.bitmaskclient.test; import java.io.*; -import se.leap.bitmaskclient.*; +import se.leap.bitmaskclient.R; public class TestDashboardIntegration extends BaseTestDashboardFragment { diff --git a/app/src/androidTest/java/se/leap/bitmaskclient/test/UserStatusTestController.java b/app/src/androidTest/java/se/leap/bitmaskclient/test/UserStatusTestController.java index f5cb2260..3b0daf9a 100644 --- a/app/src/androidTest/java/se/leap/bitmaskclient/test/UserStatusTestController.java +++ b/app/src/androidTest/java/se/leap/bitmaskclient/test/UserStatusTestController.java @@ -4,7 +4,7 @@ import android.view.*; import com.robotium.solo.*; -import se.leap.bitmaskclient.*; +import se.leap.bitmaskclient.R; import static junit.framework.Assert.assertTrue; diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/EipStatusTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/EipStatusTest.java new file mode 100644 index 00000000..15085b46 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/eip/EipStatusTest.java @@ -0,0 +1,96 @@ +package se.leap.bitmaskclient.eip; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.R; + +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_AUTH_FAILED; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_START; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_VPNPAUSED; +import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import static de.blinkt.openvpn.core.ConnectionStatus.UNKNOWN_LEVEL; +import static junit.framework.Assert.assertTrue; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTING; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTED; +import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.UNKNOWN; + +/** + * Created by cyberta on 06.12.17. + * TODO: Mock AsyncTask + */ +@RunWith(MockitoJUnitRunner.class) +public class EipStatusTest { + + EipStatus eipStatus; + + @Before + public void setUp() { + eipStatus = EipStatus.getInstance(); + + } + + @Test + public void testUpdateState_LEVEL_CONNECTED() throws Exception { + eipStatus.updateState("CONNECTED", "", R.string.state_connected, LEVEL_CONNECTED ); + assertTrue("LEVEL_CONNECTED eipLevel", eipStatus.getEipLevel() == EipStatus.EipLevel.CONNECTED); + assertTrue("LEVEL_CONNECTED level", eipStatus.getLevel() == LEVEL_CONNECTED); + assertTrue("LEVEL_CONNECTED localizedResId", eipStatus.getLocalizedResId() == R.string.state_connected); + assertTrue("LEVEL_CONNECTED state", eipStatus.getState().equals("CONNECTED")); + } + + @Test(expected= IllegalStateException.class) + public void testUpdateState_LEVEL_VPNPAUSED() throws Exception { + VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, LEVEL_VPNPAUSED); + } + + @Test + public void testUpdateState_LEVEL_CONNECTING_SERVER_REPLIED() throws Exception { + eipStatus.updateState("ADD_ROUTES", "", 0, LEVEL_CONNECTING_SERVER_REPLIED ); + assertTrue("LEVEL_CONNECTING_SERVER_REPLIED eipLevel", eipStatus.getEipLevel() == CONNECTING); + assertTrue("LEVEL_CONNECTING_SERVER_REPLIED level", eipStatus.getLevel() == LEVEL_CONNECTING_SERVER_REPLIED); + } + + @Test + public void testUpdateState_LEVEL_CONNECTING_NO_SERVER_REPLY_YET() throws Exception { + eipStatus.updateState("ADD_ROUTES", "", 0, LEVEL_CONNECTING_NO_SERVER_REPLY_YET ); + assertTrue("LEVEL_CONNECTING_SERVER_REPLIED eipLevel", eipStatus.getEipLevel() == CONNECTING); + assertTrue("LEVEL_CONNECTING_SERVER_REPLIED level", eipStatus.getLevel() == LEVEL_CONNECTING_NO_SERVER_REPLY_YET); + } + @Test + public void testUpdateState_LEVEL_START() throws Exception { + eipStatus.updateState("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START); + assertTrue("LEVEL_START eipLevel", eipStatus.getEipLevel() == CONNECTING); + assertTrue("LEVEL_START level", eipStatus.getLevel() == LEVEL_START); + } + + @Test + public void testUpdateState_LEVEL_WAITING_FOR_USER_INPUT() throws Exception { + eipStatus.updateState("NEED", "", 0, LEVEL_WAITING_FOR_USER_INPUT); + assertTrue("LEVEL_WAITING_FOR_USER_INPUT eipLevel", eipStatus.getEipLevel() == CONNECTING); + assertTrue("LEVEL_WAITING_FOR_USER_INPUT", eipStatus.getLevel() == LEVEL_WAITING_FOR_USER_INPUT); + } + + @Test + public void testUpdateState_LEVEL_AUTH_FAILED() throws Exception { + eipStatus.updateState("AUTH_FAILED", "", R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); + assertTrue("AUTH_FAILED eipLevel", eipStatus.getEipLevel() == DISCONNECTED); + assertTrue("AUTH_FAILED level", eipStatus.getLevel() == LEVEL_AUTH_FAILED); + } + + + @Test + public void testUpdateState_UNKNOWN_LEVEL() throws Exception { + eipStatus.updateState("UNKNOWN", "", 0, ConnectionStatus.UNKNOWN_LEVEL); + assertTrue("UNKNOWN_LEVEL eipLevel", eipStatus.getEipLevel() == UNKNOWN); + assertTrue("UNKNOWN_LEVEL level", eipStatus.getLevel() == UNKNOWN_LEVEL); + } + +} \ No newline at end of file -- 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 --- app/build.gradle | 5 +- .../de/blinkt/openvpn/core/OpenVPNService.java | 4 +- .../java/se/leap/bitmaskclient/BitmaskApp.java | 47 ++++++ .../java/se/leap/bitmaskclient/VpnFragment.java | 23 ++- .../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 +++++++++++++++++++-- build.gradle | 3 + 9 files changed, 226 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cdeccb6c..718e9133 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -75,8 +75,9 @@ dependencies { compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'mbanje.kurt:fabbutton:1.1.4' - compile 'com.android.support:support-annotations:25.3.1' - compile 'com.android.support:support-v4:26.0.0-alpha1' + compile "com.android.support:support-core-utils:26.1.0" + compile 'com.android.support:support-annotations:26.1.0' + compile 'com.android.support:support-v4:26.1.0' } def processFileInplace(file, Closure processText) { diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 86e1bb02..f52c30d9 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -243,8 +243,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac //noinspection NewApi nbuilder.setChannelId(channel); if (mProfile != null) - //noinspection NewApi - nbuilder.setShortcutId(mProfile.getUUIDString()); + //noinspection NewApi + nbuilder.setShortcutId(mProfile.getUUIDString()); } 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/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 57c066aa..77aa17c1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -35,15 +35,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import org.jetbrains.annotations.NotNull; - import java.util.Observable; import java.util.Observer; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; @@ -55,11 +52,7 @@ import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VoidVpnService; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.BLOCKING; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTED; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.CONNECTING; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTED; -import static se.leap.bitmaskclient.eip.EipStatus.EipLevel.DISCONNECTING; +import static se.leap.bitmaskclient.eip.Constants.ACTION_STOP_BLOCKING_VPN; public class VpnFragment extends Fragment implements Observer { @@ -160,7 +153,7 @@ public class VpnFragment extends Fragment implements Observer { handleSwitchOff(); else handleSwitchOn(); - + //FIXME ONBOOT IS BROKEN! saveStatus(eip_status.isConnected() || eip_status.isConnecting()); } @@ -197,6 +190,7 @@ public class VpnFragment extends Fragment implements Observer { } else if (eip_status.isConnected()) { askToStopEIP(); } else if (eip_status.isBlocking()) { + //FIXME DEAD CODE stop(); } else { updateIcon(); @@ -230,14 +224,19 @@ public class VpnFragment extends Fragment implements Observer { } private void stop() { - if (eip_status.isBlockingVpnEstablished()) { - Log.d(TAG, "stop VoidVpn!"); - VoidVpnService.stop(); + stopBlockingVpn(); } disconnect(); } + private void stopBlockingVpn() { + Log.d(TAG, "stop VoidVpn!"); + Intent stopVoidVpnIntent = new Intent(dashboard, VoidVpnService.class); + stopVoidVpnIntent.setAction(ACTION_STOP_BLOCKING_VPN); + dashboard.startService(stopVoidVpnIntent); + } + private void disconnect() { ProfileManager.setConntectedVpnProfileDisconnected(dashboard); if (mService != null) { 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; + } + } } diff --git a/build.gradle b/build.gradle index dc2396d7..7917b9d6 100644 --- a/build.gradle +++ b/build.gradle @@ -16,5 +16,8 @@ allprojects { repositories { mavenCentral() jcenter() + maven { + url "https://maven.google.com" + } } } -- cgit v1.2.3 From 824ed42e0ed55e10cc2b6efc4d4bb20211147f64 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 12 Dec 2017 16:20:13 +0100 Subject: 8742 fix broken onboot feature --- app/src/main/java/se/leap/bitmaskclient/VpnFragment.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 77aa17c1..f857f966 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -67,7 +67,7 @@ public class VpnFragment extends Fragment implements Observer { @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; @@ -143,8 +143,7 @@ public class VpnFragment extends Fragment implements Observer { } private void saveStatus(boolean restartOnBoot) { - //boolean is_on = eip_status.isConnected() || eip_status.isConnecting() || eip_status.isBlocking(); - Dashboard.preferences.edit().putBoolean(Constants.RESTART_ON_BOOT, restartOnBoot).commit(); + Dashboard.preferences.edit().putBoolean(Constants.RESTART_ON_BOOT, restartOnBoot).apply(); } @OnClick(R.id.vpn_main_button) @@ -153,8 +152,6 @@ public class VpnFragment extends Fragment implements Observer { handleSwitchOff(); else handleSwitchOn(); - //FIXME ONBOOT IS BROKEN! - saveStatus(eip_status.isConnected() || eip_status.isConnecting()); } private void handleSwitchOn() { @@ -189,9 +186,6 @@ public class VpnFragment extends Fragment implements Observer { askPendingStartCancellation(); } else if (eip_status.isConnected()) { askToStopEIP(); - } else if (eip_status.isBlocking()) { - //FIXME DEAD CODE - stop(); } else { updateIcon(); } @@ -217,13 +211,12 @@ public class VpnFragment extends Fragment implements Observer { public void startEipFromScratch() { wants_to_connect = false; - //eip_status.setEipLevel(BLOCKING); - saveStatus(true); eipCommand(Constants.ACTION_START_EIP); } private void stop() { + saveStatus(false); if (eip_status.isBlockingVpnEstablished()) { stopBlockingVpn(); } -- cgit v1.2.3 From edb50a2d375cc5b9d01ddeed08deea2e9b7cb043 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 14 Dec 2017 14:11:01 +0100 Subject: #8742 remove dead code --- app/src/main/java/se/leap/bitmaskclient/VpnFragment.java | 2 -- .../se/leap/bitmaskclient/userstatus/SessionDialog.java | 13 +------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index f857f966..a8a9ac67 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -137,7 +137,6 @@ 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); } @@ -160,7 +159,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?!"); 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 37744927..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())) { @@ -159,8 +155,7 @@ public class SessionDialog extends DialogFragment { public void logIn(String username, String password); public void signUp(String username, String password); - //FIXME: can we remove this method? - 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(); - } } -- cgit v1.2.3 From 63d806d5e7de82526d9376925dcb17dac819aa45 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 14 Dec 2017 14:12:05 +0100 Subject: #8742 cleanup camelCase --- app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java index 943e877a..70358580 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -19,16 +19,16 @@ public class OnBootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { preferences = context.getSharedPreferences(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE); - boolean provider_configured = !preferences.getString(VPN_CERTIFICATE, "").isEmpty(); - boolean start_on_boot = preferences.getBoolean(RESTART_ON_BOOT, false); + boolean providerConfigured = !preferences.getString(VPN_CERTIFICATE, "").isEmpty(); + boolean startOnBoot = preferences.getBoolean(RESTART_ON_BOOT, false); boolean isAlwaysOnConfigured = preferences.getBoolean(IS_ALWAYS_ON, false); - Log.d("OpenVPN", "OpenVPN onBoot intent received. Provider configured? " + provider_configured + " Start on boot? " + start_on_boot + " isAlwaysOn feature configured: " + isAlwaysOnConfigured); - if (provider_configured) { + 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 (start_on_boot) { + if (startOnBoot) { Intent dashboard_intent = new Intent(context, Dashboard.class); dashboard_intent.putExtra(RESTART_ON_BOOT, true); dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); -- cgit v1.2.3 From b60de97967b7a252762640a8917c24e4211df7c1 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Thu, 14 Dec 2017 14:32:46 +0100 Subject: #8742 fix lint issue in OnBootReceiver --- app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java index 70358580..b151f40a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -6,6 +6,7 @@ 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.eip.Constants.IS_ALWAYS_ON; import static se.leap.bitmaskclient.eip.Constants.RESTART_ON_BOOT; import static se.leap.bitmaskclient.eip.Constants.VPN_CERTIFICATE; @@ -14,10 +15,13 @@ public class OnBootReceiver extends BroadcastReceiver { SharedPreferences preferences; - // Debug: su && 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(Dashboard.SHARED_PREFERENCES, Context.MODE_PRIVATE); boolean providerConfigured = !preferences.getString(VPN_CERTIFICATE, "").isEmpty(); boolean startOnBoot = preferences.getBoolean(RESTART_ON_BOOT, false); -- 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(-) 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