diff options
author | cyberta <cyberta@riseup.net> | 2021-02-28 21:29:39 +0000 |
---|---|---|
committer | cyberta <cyberta@riseup.net> | 2021-02-28 21:29:39 +0000 |
commit | 665b965194cf24d01b02789adf286950da2e91b2 (patch) | |
tree | b31c4d03d8c2511371184d2fe059a94fb1fd2cae /app/src/main | |
parent | 8bb73089566e78a195f21f4746c9db416abf95a4 (diff) | |
parent | 401e28431fcd3a093aa34d429f5bc08abca5d422 (diff) |
Merge branch 'beta-release' into 'master'
Beta release
See merge request leap/bitmask_android!131
Diffstat (limited to 'app/src/main')
17 files changed, 560 insertions, 434 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 293c2b6d..98cc3e42 100644 --- a/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl +++ b/app/src/main/aidl/de/blinkt/openvpn/core/IOpenVPNServiceInternal.aidl @@ -23,4 +23,6 @@ interface IOpenVPNServiceInternal { boolean isVpnRunning(); + void startWithForegroundNotification(); + } diff --git a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java index b148d04d..82eb3ca9 100644 --- a/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java +++ b/app/src/main/java/de/blinkt/openvpn/LaunchVPN.java @@ -5,29 +5,25 @@ package de.blinkt.openvpn; -import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; -import android.content.SharedPreferences; import android.net.VpnService; import android.os.Build; import android.os.Bundle; -import java.io.IOException; +import androidx.annotation.StringRes; import de.blinkt.openvpn.core.ConnectionStatus; -import de.blinkt.openvpn.core.Preferences; -import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipCommand; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; +import static se.leap.bitmaskclient.eip.EIP.ERRORS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; /** @@ -57,54 +53,52 @@ import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroad */ public class LaunchVPN extends Activity { - public static final String EXTRA_KEY = "de.blinkt.openvpn.shortcutProfileUUID"; - public static final String EXTRA_NAME = "de.blinkt.openvpn.shortcutProfileName"; - public static final String EXTRA_HIDELOG = "de.blinkt.openvpn.showNoLogWindow"; - public static final String CLEARLOG = "clearlogconnect"; - - private static final int START_VPN_PROFILE = 70; private static final String TAG = LaunchVPN.class.getName(); + private VpnProfile selectedProfile; + private int selectedGateway; - private VpnProfile mSelectedProfile; - private boolean mhideLog = false; - - private boolean mCmfixed = false; - @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - startVpnFromIntent(); - } - - protected void startVpnFromIntent() { - // Resolve the intent - final Intent intent = getIntent(); final String action = intent.getAction(); + if (!Intent.ACTION_MAIN.equals(action)) { + finish(); + } - // If the intent is a request to create a shortcut, we'll do that and exit - + VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + selectedGateway = intent.getExtras().getInt(EIP_N_CLOSEST_GATEWAY, 0); + if (profileToConnect == null) { + showAlertInMainActivity(R.string.shortcut_profile_notfound); + finish(); + } else { + selectedProfile = profileToConnect; + } - if (Intent.ACTION_MAIN.equals(action)) { - // Check if we need to clear the log - if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) - VpnStatus.clearLog(); + Intent vpnIntent; + try { + vpnIntent = VpnService.prepare(this.getApplicationContext()); + } catch (NullPointerException npe) { + showAlertInMainActivity(R.string.vpn_error_establish); + finish(); + return; + } - // we got called to be the starting point, most likely a shortcut - mhideLog = intent.getBooleanExtra(EXTRA_HIDELOG, false); - VpnProfile profileToConnect = (VpnProfile) intent.getExtras().getSerializable(PROVIDER_PROFILE); + if (vpnIntent != null) { + // we don't have the permission yet to start the VPN - if (profileToConnect == null) { - VpnStatus.logError(R.string.shortcut_profile_notfound); - // show Log window to display error - showLogWindow(); - finish(); - } else { - mSelectedProfile = profileToConnect; - launchVPN(); + VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, + ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); + // Start the query + try { + startActivityForResult(vpnIntent, START_VPN_PROFILE); + } catch (ActivityNotFoundException ane) { + // Shame on you Sony! At least one user reported that + // an official Sony Xperia Arc S image triggers this exception + showAlertInMainActivity(R.string.no_vpn_support_image); } } } @@ -113,13 +107,8 @@ public class LaunchVPN extends Activity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if(requestCode==START_VPN_PROFILE) { - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean showLogWindow = prefs.getBoolean("showlogwindow", true); - - if(!mhideLog && showLogWindow) - showLogWindow(); - VPNLaunchHelper.startOpenVpn(mSelectedProfile, getBaseContext()); + if(requestCode==START_VPN_PROFILE && resultCode == Activity.RESULT_OK) { + EipCommand.launchVPNProfile(getApplicationContext(), selectedProfile, selectedGateway); finish(); } else if (resultCode == Activity.RESULT_CANCELED) { @@ -127,110 +116,29 @@ public class LaunchVPN extends Activity { VpnStatus.updateStateString("USER_VPN_PERMISSION_CANCELLED", "", R.string.state_user_vpn_permission_cancelled, ConnectionStatus.LEVEL_NOTCONNECTED); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - VpnStatus.logError(R.string.nought_alwayson_warning); - - finish(); - } - } - - void showLogWindow() { - - Intent startLW = new Intent(getBaseContext(), MainActivity.class); - startLW.putExtra(MainActivity.ACTION_SHOW_LOG_FRAGMENT, true); - startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(startLW); - - } - - void showConfigErrorDialog(int vpnok) { - AlertDialog.Builder d = new AlertDialog.Builder(this); - d.setTitle(R.string.config_error_found); - d.setMessage(vpnok); - d.setPositiveButton(android.R.string.ok, new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - - } - }); - d.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - finish(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + showAlertInMainActivity(R.string.nought_alwayson_warning); } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) - setOnDismissListener(d); - d.show(); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - private void setOnDismissListener(AlertDialog.Builder d) { - d.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - finish(); - } - }); - } - void launchVPN() { - int vpnok = mSelectedProfile.checkProfile(this); - if (vpnok != R.string.no_error_found) { - showConfigErrorDialog(vpnok); - return; - } - - Intent intent = null; - try { - intent = VpnService.prepare(this.getApplicationContext()); - } catch (NullPointerException npe) { - tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); finish(); - return; - } - - // Check if we want to fix /dev/tun - SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this); - boolean usecm9fix = prefs.getBoolean("useCM9Fix", false); - boolean loadTunModule = prefs.getBoolean("loadTunModule", false); - - if (loadTunModule) - execeuteSUcmd("insmod /system/lib/modules/tun.ko"); - - if (usecm9fix && !mCmfixed) { - execeuteSUcmd("chown system /dev/tun"); - } - - if (intent != null) { - VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, - ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); - // Start the query - try { - startActivityForResult(intent, START_VPN_PROFILE); - } catch (ActivityNotFoundException ane) { - // Shame on you Sony! At least one user reported that - // an official Sony Xperia Arc S image triggers this exception - VpnStatus.logError(R.string.no_vpn_support_image); - showLogWindow(); - } - } else { - onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); } + } + void showAlertInMainActivity(@StringRes int errorString) { + Bundle result = new Bundle(); + setErrorResult(result, errorString); + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED, result); } - private void execeuteSUcmd(String command) { - try { - ProcessBuilder pb = new ProcessBuilder("su", "-c", command); - Process p = pb.start(); - int ret = p.waitFor(); - if (ret == 0) - mCmfixed = true; - } catch (InterruptedException | IOException e) { - VpnStatus.logException("SU command", e); - } + /** + * helper function to add error to result bundle + * + * @param result - result of an action + * @param errorMessageId - id of string resource describing the error + */ + void setErrorResult(Bundle result, @StringRes int errorMessageId) { + VpnStatus.logError(errorMessageId); + result.putString(ERRORS, getResources().getString(errorMessageId)); + result.putBoolean(BROADCAST_RESULT_KEY, false); } }
\ No newline at end of file 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 2c1a65dc..9ed2054e 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -8,12 +8,10 @@ package de.blinkt.openvpn.core; import android.Manifest.permission; import android.annotation.TargetApi; import android.app.Notification; -import android.app.UiModeManager; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; -import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.VpnService; @@ -24,12 +22,13 @@ import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.RemoteException; -import androidx.annotation.RequiresApi; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.Inet6Address; @@ -45,9 +44,10 @@ import de.blinkt.openvpn.core.VpnStatus.StateListener; import de.blinkt.openvpn.core.connection.Connection; import de.blinkt.openvpn.core.connection.Obfs4Connection; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.eip.VpnNotificationManager; -import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import se.leap.bitmaskclient.firewall.FirewallManager; +import se.leap.bitmaskclient.pluggableTransports.Shapeshifter; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; @@ -61,7 +61,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; - public static final String NOTIFICATION_CHANNEL_BG_ID = "openvpn_bg"; public static final String NOTIFICATION_CHANNEL_NEWSTATUS_ID = "openvpn_newstat"; public static final String VPNSERVICE_TUN = "vpnservice-tun"; public final static String ORBOT_PACKAGE_NAME = "org.torproject.android"; @@ -92,11 +91,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac private Shapeshifter shapeshifter; private FirewallManager firewallManager; - private static final int PRIORITY_MIN = -2; - private static final int PRIORITY_DEFAULT = 0; - private static final int PRIORITY_MAX = 2; - - private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() { @Override @@ -118,6 +112,11 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac public boolean isVpnRunning() throws RemoteException { return OpenVPNService.this.isVpnRunning(); } + + @Override + public void startWithForegroundNotification() throws RemoteException { + OpenVPNService.this.startWithForegroundNotification(); + } }; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java @@ -197,13 +196,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac firewallManager.stop(); } - private boolean runningOnAndroidTV() { - UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); - if (uiModeManager == null) - return false; - return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; - } - synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) { // Registers BroadcastReceiver to track network connection changes. IntentFilter filter = new IntentFilter(); @@ -218,8 +210,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - addLollipopCMListener(); */ } synchronized void unregisterDeviceStateReceiver() { @@ -235,9 +225,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } mDeviceStateReceiver = null; - /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - removeLollipopCMListener();*/ - } @Override @@ -281,6 +268,13 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } @Override + public void startWithForegroundNotification() { + // Always show notification here to avoid problem with startForeground timeout + notificationManager.createOpenVpnNotificationChannel(); + notificationManager.buildForegroundServiceNotification(EipStatus.getInstance().getLevel(), this::onNotificationBuild); + } + + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false)) @@ -334,6 +328,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } else { /* The intent is null when we are set as always-on or the service has been restarted. */ Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash."); + startWithForegroundNotification(); + mProfile = VpnStatus.getLastConnectedVpnProfile(this); VpnStatus.logInfo(R.string.service_restarted); @@ -375,9 +371,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac } private void startOpenVPN() { - /** - * see change above (l. 292 ff) - */ //TODO: investigate how connections[n] with n>0 get called during vpn setup (on connection refused?) // Do we need to check if there's any obfs4 connection in mProfile.mConnections and start // the dispatcher here? Can we start the dispatcher at a later point of execution, e.g. when @@ -524,8 +517,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac @Override public void onCreate() { super.onCreate(); - notificationManager = new VpnNotificationManager(this, this); - notificationManager.createOpenVpnNotificationChannel(); + notificationManager = new VpnNotificationManager(this); firewallManager = new FirewallManager(this, true); } @@ -543,9 +535,8 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac // Just in case unregister for state VpnStatus.removeStateListener(this); VpnStatus.flushLog(); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_BG_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); firewallManager.onDestroy(); + notificationManager.cancelAll(); } private String getTunConfigString() { @@ -1013,14 +1004,10 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac if (mProcessThread == null && !mNotificationAlwaysVisible) return; - String channel = NOTIFICATION_CHANNEL_NEWSTATUS_ID; // Display byte count only after being connected - if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); - if (!runningOnAndroidTV()) - channel = NOTIFICATION_CHANNEL_BG_ID; firewallManager.start(); } else { mDisplayBytecount = false; @@ -1033,7 +1020,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac VpnStatus.getLastCleanLogMessage(this), level, 0, - channel); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1064,7 +1051,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac null, LEVEL_CONNECTED, mConnecttime, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } } @@ -1108,7 +1095,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac getString(resid), LEVEL_WAITING_FOR_USER_INPUT, 0, - NOTIFICATION_CHANNEL_BG_ID); + NOTIFICATION_CHANNEL_NEWSTATUS_ID); } @@ -1117,11 +1104,6 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); - } - public void trigger_url_open(String info) { /* String channel = NOTIFICATION_CHANNEL_USERREQ_ID; diff --git a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 7c742746..540ca043 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -7,7 +7,6 @@ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; import android.os.Build; import java.io.File; @@ -18,7 +17,6 @@ import java.util.Arrays; import java.util.Vector; import se.leap.bitmaskclient.R; -import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { private static final String MININONPIEVPN = "nopie_openvpn"; @@ -120,7 +118,6 @@ public class VPNLaunchHelper { return false; } - return true; } catch (IOException e) { VpnStatus.logException(e); @@ -129,20 +126,6 @@ public class VPNLaunchHelper { } - - public static void startOpenVpn(VpnProfile startprofile, Context context) { - Intent startVPN = startprofile.prepareStartService(context); - if (startVPN != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - //noinspection NewApi - context.startForegroundService(startVPN); - else - context.startService(startVPN); - - } - } - - public static String getConfigFilePath(Context context) { return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 676e6c82..126c4a98 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -20,13 +20,14 @@ package se.leap.bitmaskclient.base; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.Log; + import androidx.annotation.StringRes; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -35,24 +36,27 @@ import java.util.Observable; import java.util.Observer; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; -import se.leap.bitmaskclient.eip.EIP; -import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.eip.EipSetupListener; -import se.leap.bitmaskclient.eip.EipSetupObserver; import se.leap.bitmaskclient.base.fragments.EipFragment; import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment; import se.leap.bitmaskclient.base.fragments.LogFragment; +import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; -import se.leap.bitmaskclient.providersetup.activities.LoginActivity; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.base.fragments.MainActivityErrorDialog; +import se.leap.bitmaskclient.eip.EIP; +import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipSetupListener; +import se.leap.bitmaskclient.eip.EipSetupObserver; +import se.leap.bitmaskclient.providersetup.activities.LoginActivity; +import se.leap.bitmaskclient.providersetup.models.LeapSRPSession; +import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; +import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; @@ -61,16 +65,14 @@ import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; -import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; -import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; -import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; public class MainActivity extends AppCompatActivity implements EipSetupListener, Observer, ExcludeAppsFragment.ExcludedAppsCallback { @@ -256,7 +258,14 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, break; case EIP_ACTION_PREPARE_VPN: if (resultCode == RESULT_CANCELED) { - showMainActivityErrorDialog(getString(R.string.vpn_error_establish), ERROR_VPN_PREPARE); + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error, ERROR_VPN_PREPARE); + } + break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + String error = resultData.getString(ERRORS); + showMainActivityErrorDialog(error); } break; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index 4ff80ea6..615221ae 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -52,6 +52,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; +import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VpnStatus; @@ -175,12 +176,18 @@ public class EipFragment extends Fragment implements Observer { View view = inflater.inflate(R.layout.f_eip, container, false); unbinder = ButterKnife.bind(this, view); - Bundle arguments = getArguments(); - if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) { - arguments.remove(ASK_TO_CANCEL_VPN); - setArguments(arguments); - askToStopEIP(); + try { + Bundle arguments = getArguments(); + if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) { + arguments.remove(ASK_TO_CANCEL_VPN); + setArguments(arguments); + askToStopEIP(); + } + } catch (IllegalStateException e) { + // probably setArguments failed because the fragments state is already saved + e.printStackTrace(); } + restoreFromSavedInstance(savedInstanceState); return view; } @@ -319,10 +326,7 @@ public class EipFragment extends Fragment implements Observer { } else { EipCommand.startVPN(context.getApplicationContext(), false); } - vpnStateImage.showProgress(); - routedText.setVisibility(GONE); - vpnRoute.setVisibility(GONE); - colorBackgroundALittle(); + EipStatus.getInstance().updateState("UI_CONNECTING", "", 0, ConnectionStatus.LEVEL_START); } protected void stopEipIfPossible() { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index 4b307f23..f036b411 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -111,15 +111,15 @@ public class MainActivityErrorDialog extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); Context applicationContext = getContext().getApplicationContext(); - builder.setMessage(reasonToFail) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - }); + builder.setMessage(reasonToFail); switch (downloadError) { case ERROR_INVALID_VPN_CERTIFICATE: builder.setPositiveButton(R.string.update_certificate, (dialog, which) -> ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider)); + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); break; case NO_MORE_GATEWAYS: + builder.setNegativeButton(R.string.cancel, (dialog, id) -> {}); if (provider.supportsPluggableTransports()) { if (getUsePluggableTransports(applicationContext)) { builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> { @@ -139,9 +139,7 @@ public class MainActivityErrorDialog extends DialogFragment { } break; case ERROR_VPN_PREPARE: - builder.setPositiveButton(R.string.retry, (dialog, which) -> { - EipCommand.startVPN(applicationContext, false); - }); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { }); break; default: break; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java index e60019fc..a0d295bd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java @@ -80,7 +80,7 @@ public interface Constants { 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_ACTION_PREPARE_VPN = "se.leap.bitmaskclient.EIP_ACTION_PREPARE_VPN"; - String EIP_ACTION_CONFIGURE_TETHERING = "se.leap.bitmaskclient.EIP_ACTION_CONFIGURE_TETHERING"; + String EIP_ACTION_LAUNCH_VPN = "se.leap.bitmaskclient.EIP_ACTION_LAUNCH_VPN"; String EIP_RECEIVER = "EIP.RECEIVER"; String EIP_REQUEST = "EIP.REQUEST"; @@ -157,6 +157,7 @@ public interface Constants { // JSON KEYS ///////////////////////////////////////////// String IP_ADDRESS = "ip_address"; + String IP_ADDRESS6 = "ip_address6"; String REMOTE = "remote"; String PORTS = "ports"; String PROTOCOLS = "protocols"; 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 66b7c6cf..74226250 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -22,16 +22,19 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.net.VpnService; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.WorkerThread; import androidx.core.app.JobIntentService; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import android.util.Log; import org.json.JSONException; import org.json.JSONObject; @@ -43,26 +46,31 @@ import java.util.Observer; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.IOpenVPNServiceInternal; import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.connection.Connection; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.OnBootReceiver; import se.leap.bitmaskclient.base.models.ProviderObservable; -import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; +import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; +import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; @@ -75,13 +83,13 @@ import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES; -import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; -import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_PROFILE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.NO_MORE_GATEWAYS; import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.ensureNotOnMainThread; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; /** * EIP is the abstract base class for interacting with and managing the Encrypted @@ -106,6 +114,7 @@ public final class EIP extends JobIntentService implements Observer { // Service connection to OpenVpnService, shared between threads private volatile OpenVpnServiceConnection openVpnServiceConnection; private WeakReference<ResultReceiver> mResultRef = new WeakReference<>(null); + private volatile VoidVpnServiceConnection voidVpnServiceConnection; /** * Unique job ID for this service. @@ -116,7 +125,8 @@ public final class EIP extends JobIntentService implements Observer { UNKNOWN, ERROR_INVALID_VPN_CERTIFICATE, NO_MORE_GATEWAYS, - ERROR_VPN_PREPARE + ERROR_VPN_PREPARE, + ERROR_INVALID_PROFILE } /** @@ -146,6 +156,10 @@ public final class EIP extends JobIntentService implements Observer { openVpnServiceConnection.close(); openVpnServiceConnection = null; } + if (voidVpnServiceConnection != null) { + voidVpnServiceConnection.close(); + voidVpnServiceConnection = null; + } } /** @@ -175,7 +189,7 @@ public final class EIP extends JobIntentService implements Observer { int nClosestGateway; switch (action) { case EIP_ACTION_START: - boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, true); + boolean earlyRoutes = intent.getBooleanExtra(EIP_EARLY_ROUTES, false); nClosestGateway = intent.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0); startEIP(earlyRoutes, nClosestGateway); break; @@ -195,8 +209,9 @@ public final class EIP extends JobIntentService implements Observer { disconnect(); earlyRoutes(); break; - case EIP_ACTION_CONFIGURE_TETHERING: - Log.d(TAG, "TODO: implement tethering configuration"); + case EIP_ACTION_LAUNCH_VPN: + VpnProfile profile = (VpnProfile) intent.getSerializableExtra(PROVIDER_PROFILE); + launchProfile(profile); break; } } @@ -211,11 +226,11 @@ public final class EIP extends JobIntentService implements Observer { @SuppressLint("ApplySharedPref") private void startEIP(boolean earlyRoutes, int nClosestGateway) { Log.d(TAG, "start EIP with early routes: " + earlyRoutes + " and nClosest Gateway: " + nClosestGateway); + Bundle result = new Bundle(); if (!eipStatus.isBlockingVpnEstablished() && earlyRoutes) { - earlyRoutes(); + earlyRoutes(result); } - Bundle result = new Bundle(); if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) { preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); } @@ -234,12 +249,11 @@ public final class EIP extends JobIntentService implements Observer { } Gateway gateway = gatewaysManager.select(nClosestGateway); - - if (launchActiveGateway(gateway, nClosestGateway)) { - tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); - } else { - setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + launchActiveGateway(gateway, nClosestGateway, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) { tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_CANCELED, result); + } else { + tellToReceiverOrBroadcast(this, EIP_ACTION_START, RESULT_OK); } } @@ -250,9 +264,19 @@ public final class EIP extends JobIntentService implements Observer { private void startEIPAlwaysOnVpn() { GatewaysManager gatewaysManager = new GatewaysManager(getApplicationContext()); Gateway gateway = gatewaysManager.select(0); + Bundle result = new Bundle(); - if (!launchActiveGateway(gateway, 0)) { - Log.d(TAG, "startEIPAlwaysOnVpn no active profile available!"); + launchActiveGateway(gateway, 0, result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + VpnStatus.logWarning("ALWAYS-ON VPN: " + getString(R.string.no_vpn_profiles_defined)); + } + } + + private void earlyRoutes() { + Bundle result = new Bundle(); + earlyRoutes(result); + if (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)){ + tellToReceiverOrBroadcast(this, EIP_ACTION_START_BLOCKING_VPN, RESULT_CANCELED, result); } } @@ -260,10 +284,27 @@ public final class EIP extends JobIntentService implements Observer { * Early routes are routes that block traffic until a new * VpnService is started properly. */ - private void earlyRoutes() { - Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); - voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(voidVpnLauncher); + private void earlyRoutes(Bundle result) { + Intent blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + if (blockingIntent == null) { + try { + initVoidVpnServiceConnection(); + Intent voidVpnService = new Intent(getApplicationContext(), VoidVpnService.class); + voidVpnService.setAction(EIP_ACTION_START_BLOCKING_VPN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + getApplicationContext().startForegroundService(voidVpnService); + voidVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(voidVpnService); + } + } catch (InterruptedException | IllegalStateException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + Intent voidVpnLauncher = new Intent(getApplicationContext(), VoidVpnLauncher.class); + voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(voidVpnLauncher); + } } /** @@ -271,21 +312,59 @@ public final class EIP extends JobIntentService implements Observer { * * @param gateway to connect to */ - private boolean launchActiveGateway(Gateway gateway, int nClosestGateway) { + private void launchActiveGateway(Gateway gateway, int nClosestGateway, Bundle result) { VpnProfile profile; Connection.TransportType transportType = getUsePluggableTransports(this) ? OBFS4 : OPENVPN; if (gateway == null || (profile = gateway.getProfile(transportType)) == null) { - return false; + setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name)); + return; } - Intent intent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); - intent.putExtra(PROVIDER_PROFILE, profile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, nClosestGateway); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - return true; + Intent intent; + try { + intent = VpnService.prepare(getApplicationContext()); + } catch (NullPointerException npe) { + setErrorResult(result, ERROR_VPN_PREPARE.toString(), R.string.vpn_error_establish); + return; + } + if (intent == null) { + // vpn has been successfully prepared + + //inform EipSetupObserver about vpn connecting attempt + Intent setupObserverIntent = new Intent(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); + setupObserverIntent.putExtra(PROVIDER_PROFILE, profile); + setupObserverIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + LocalBroadcastManager.getInstance(this).sendBroadcast(setupObserverIntent); + + // Check if we need to clear the log + if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + VpnStatus.clearLog(); + + // check profile configuration + int vpnok = profile.checkProfile(this); + if (vpnok != R.string.no_error_found) { + VpnStatus.logError(R.string.config_error_found); + VpnStatus.logError(vpnok); + setErrorResult(result, ERROR_INVALID_PROFILE.toString(), 0); + return; + } + + //launch profile + launchProfile(profile, result); + + } else { + // vpn permission is missing + Intent permissionIntent = new Intent(getApplicationContext(), LaunchVPN.class); + permissionIntent.setAction(Intent.ACTION_MAIN); + permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + permissionIntent.putExtra(PROVIDER_PROFILE, profile); + permissionIntent.putExtra(EIP_N_CLOSEST_GATEWAY, nClosestGateway); + startActivity(permissionIntent); + } } + /** * Stop VPN * First checks if the OpenVpnConnection is open then @@ -352,14 +431,16 @@ public final class EIP extends JobIntentService implements Observer { void setErrorResult(Bundle result, String errorId, @StringRes int errorMessageId, Object... args) { JSONObject errorJson = new JSONObject(); try { - String errorMessage; - if (args != null) { - errorMessage = getResources().getString(errorMessageId, args); - } else { - errorMessage = getResources().getString(errorMessageId); + if (errorMessageId != 0) { + String errorMessage; + if (args != null) { + errorMessage = getResources().getString(errorMessageId, args); + } else { + errorMessage = getResources().getString(errorMessageId); + } + VpnStatus.logWarning("[EIP] error: " + errorMessage); + errorJson.put(ERRORS, errorMessage); } - VpnStatus.logWarning("[EIP] error: " + errorMessage); - errorJson.put(ERRORS, errorMessage); errorJson.put(ERRORID, errorId); } catch (JSONException e) { e.printStackTrace(); @@ -409,6 +490,41 @@ public final class EIP extends JobIntentService implements Observer { return false; } + /** + * binds OpenVPNService to this service, starts it as a foreground service with a profile + * @param vpnProfile OpenVPN profile used to create a VPN connection + * @param result Bundle containing information about possible errors + */ + private void launchProfile(VpnProfile vpnProfile, Bundle result) { + Intent startVPN = vpnProfile.prepareStartService(getApplicationContext()); + if (startVPN != null) { + try { + initOpenVpnServiceConnection(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //noinspection NewApi + getApplicationContext().startForegroundService(startVPN); + openVpnServiceConnection.getService().startWithForegroundNotification(); + } else { + getApplicationContext().startService(startVPN); + } + } catch (InterruptedException | IllegalStateException | RemoteException e) { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } else { + setErrorResult(result, R.string.vpn_error_establish, null); + } + } + + private void launchProfile(VpnProfile vpnProfile) { + Bundle bundle = new Bundle(); + launchProfile(vpnProfile, bundle); + if (bundle.containsKey(BROADCAST_RESULT_KEY) && !bundle.getBoolean(BROADCAST_RESULT_KEY)) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_CANCELED, bundle); + } else { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_LAUNCH_VPN, RESULT_OK); + } + } + private @StringRes int getStringResourceForNoMoreGateways() { if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { @@ -437,6 +553,69 @@ public final class EIP extends JobIntentService implements Observer { } /** + * Assigns a new VoidVpnServiceConnection to EIP's member variable voidVpnServiceConnection. + * Only one thread at a time can create the service connection, that will be shared between threads + * + * @throws InterruptedException thrown if thread gets interrupted + * @throws IllegalStateException thrown if this method was not called from a background thread + */ + private void initVoidVpnServiceConnection() throws InterruptedException, IllegalStateException { + if (voidVpnServiceConnection == null) { + Log.d(TAG, "serviceConnection is still null"); + voidVpnServiceConnection = new VoidVpnServiceConnection(this); + } + } + + public static class VoidVpnServiceConnection implements Closeable { + private final Context context; + private ServiceConnection serviceConnection; + private VoidVpnService voidVpnService; + + VoidVpnServiceConnection(Context context) throws InterruptedException, IllegalStateException { + this.context = context; + ensureNotOnMainThread(context); + Log.d(TAG, "initSynchronizedServiceConnection!"); + initSynchronizedServiceConnection(context); + } + + @Override + public void close() { + context.unbindService(serviceConnection); + } + + private void initSynchronizedServiceConnection(final Context context) throws InterruptedException { + final BlockingQueue<VoidVpnService> blockingQueue = new LinkedBlockingQueue<>(1); + this.serviceConnection = new ServiceConnection() { + volatile boolean mConnectedAtLeastOnce = false; + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (!mConnectedAtLeastOnce) { + mConnectedAtLeastOnce = true; + try { + VoidVpnService.VoidVpnServiceBinder binder = (VoidVpnService.VoidVpnServiceBinder) service; + blockingQueue.put(binder.getService()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + Intent intent = new Intent(context, VoidVpnService.class); + context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); + voidVpnService = blockingQueue.take(); + } + + public VoidVpnService getService() { + return voidVpnService; + } + } + + /** * Creates a service connection to OpenVpnService. * The constructor blocks until the service is bound to the given Context. * Pattern stolen from android.security.KeyChain.java diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java index 0650e8cd..46704419 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipCommand.java @@ -9,14 +9,17 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import de.blinkt.openvpn.VpnProfile; + import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CONFIGURE_TETHERING; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_RECEIVER; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; /** * Use this class to send commands to EIP @@ -73,6 +76,15 @@ public class EipCommand { execute(context, EIP_ACTION_STOP); } + public static void launchVPNProfile(@NonNull Context context, VpnProfile vpnProfile, Integer closestGateway) { + Intent baseIntent = new Intent(); + baseIntent.putExtra(PROVIDER_PROFILE, vpnProfile); + baseIntent.putExtra(EIP_N_CLOSEST_GATEWAY, closestGateway); + execute(context, EIP_ACTION_LAUNCH_VPN, null, baseIntent); + } + + public static void launchVoidVPN(@NonNull Context context) { execute(context, EIP_ACTION_START_BLOCKING_VPN);} + @VisibleForTesting public static void stopVPN(@NonNull Context context, ResultReceiver resultReceiver) { execute(context, EIP_ACTION_STOP, resultReceiver, null); @@ -87,13 +99,4 @@ public class EipCommand { execute(context, EIP_ACTION_CHECK_CERT_VALIDITY, resultReceiver, null); } - public static void configureTethering(@NonNull Context context) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - - @VisibleForTesting - public static void configureTethering(@NonNull Context context, ResultReceiver resultReceiver) { - execute(context, EIP_ACTION_CONFIGURE_TETHERING); - } - } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index f35e5e30..1ad5f7d2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -54,10 +54,12 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETU import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; +import static se.leap.bitmaskclient.base.models.Constants.EIP_N_CLOSEST_GATEWAY; import static se.leap.bitmaskclient.base.models.Constants.EIP_REQUEST; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE; @@ -158,14 +160,14 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); if (EipStatus.getInstance().isDisconnected()) { - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); } break; case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); PreferenceHelper.storeProviderInPreferences(preferences, provider); - EipCommand.startVPN(context.getApplicationContext(), true); + EipCommand.startVPN(context.getApplicationContext(), false); break; case CORRECTLY_DOWNLOADED_GEOIP_JSON: provider = resultData.getParcelable(PROVIDER_KEY); @@ -212,14 +214,18 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case EIP_ACTION_START_ALWAYS_ON_VPN: if (resultCode == RESULT_CANCELED) { //setup failed - if (error == EIP.EIPErrors.NO_MORE_GATEWAYS) { - finishGatewaySetup(false); - EipCommand.startBlockingVPN(context.getApplicationContext()); - } else { - //FIXME: - finishGatewaySetup(false); - EipCommand.stopVPN(context); - EipStatus.refresh(); + switch (error) { + case NO_MORE_GATEWAYS: + finishGatewaySetup(false); + EipCommand.startBlockingVPN(context.getApplicationContext()); + break; + case ERROR_INVALID_PROFILE: + selectNextGateway(); + break; + default: + finishGatewaySetup(false); + EipCommand.stopVPN(context); + EipStatus.refresh(); } } break; @@ -230,6 +236,13 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta EipStatus.refresh(); } break; + case EIP_ACTION_LAUNCH_VPN: + if (resultCode == RESULT_CANCELED) { + VpnStatus.logError("Error starting VpnService."); + finishGatewaySetup(false); + EipStatus.refresh(); + } + break; default: break; } @@ -252,21 +265,9 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta return; } setupVpnProfile = vpnProfile; - setupNClosestGateway.set(event.getIntExtra(Gateway.KEY_N_CLOSEST_GATEWAY, 0)); + setupNClosestGateway.set(event.getIntExtra(EIP_N_CLOSEST_GATEWAY, 0)); Log.d(TAG, "bitmaskapp add state listener"); VpnStatus.addStateListener(this); - - launchVPN(setupVpnProfile); - } - - private void launchVPN(VpnProfile vpnProfile) { - Intent intent = new Intent(context.getApplicationContext(), LaunchVPN.class); - intent.setAction(Intent.ACTION_MAIN); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(LaunchVPN.EXTRA_HIDELOG, true); - intent.putExtra(PROVIDER_PROFILE, vpnProfile); - intent.putExtra(Gateway.KEY_N_CLOSEST_GATEWAY, setupNClosestGateway.get()); - context.startActivity(intent); } @Override 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 ad84ec5a..bc123683 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipStatus.java @@ -77,7 +77,7 @@ public class EipStatus extends Observable implements VpnStatus.StateListener { currentStatus.setLocalizedResId(localizedResId); currentStatus.setLevel(level); currentStatus.setEipLevel(level); - if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state)) { + if (tmp != currentStatus.getLevel() || "RECONNECTING".equals(state) || "UI_CONNECTING".equals(state)) { refresh(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 1df54e6e..6b44856e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -55,7 +55,6 @@ import static se.leap.bitmaskclient.base.models.Constants.VERSION; public class Gateway { public final static String TAG = Gateway.class.getSimpleName(); - public final static String KEY_N_CLOSEST_GATEWAY = "N_CLOSEST_GATEWAY"; private JSONObject generalConfiguration; private JSONObject secrets; 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 e6905448..e2cd86b9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnLauncher.java @@ -3,10 +3,10 @@ package se.leap.bitmaskclient.eip; import android.app.Activity; import android.content.Intent; import android.net.VpnService; -import android.os.Build; import android.os.Bundle; -import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_PREPARE_VPN; +import static se.leap.bitmaskclient.eip.EipResultBroadcast.tellToReceiverOrBroadcast; public class VoidVpnLauncher extends Activity { @@ -19,24 +19,25 @@ public class VoidVpnLauncher extends Activity { } public void setUp() { - Intent blocking_intent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. - if (blocking_intent != null) - startActivityForResult(blocking_intent, VPN_USER_PERMISSION); + Intent blockingIntent = null; + try { + blockingIntent = VpnService.prepare(getApplicationContext()); // stops the VPN connection created by another application. + } catch (NullPointerException npe) { + tellToReceiverOrBroadcast(this.getApplicationContext(), EIP_ACTION_PREPARE_VPN, RESULT_CANCELED); + finish(); + } + if (blockingIntent != null) { + startActivityForResult(blockingIntent, VPN_USER_PERMISSION); + } else { - onActivityResult(VPN_USER_PERMISSION, RESULT_OK, null); + EipCommand.startBlockingVPN(getApplicationContext()); } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == VPN_USER_PERMISSION) { if (resultCode == RESULT_OK) { - Intent void_vpn_service = new Intent(getApplicationContext(), VoidVpnService.class); - 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); - } + EipCommand.launchVoidVPN(getApplicationContext()); } } 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 77038492..35d2b376 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -21,7 +21,9 @@ import android.app.Notification; import android.content.Intent; import android.content.SharedPreferences; import android.net.VpnService; +import android.os.Binder; import android.os.Build; +import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.system.OsConstants; import android.util.Log; @@ -53,13 +55,27 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat private EipStatus eipStatus; private VpnNotificationManager notificationManager; + private final IBinder binder = new VoidVpnServiceBinder(); + public class VoidVpnServiceBinder extends Binder { + VoidVpnService getService() { + // Return this instance of LocalService so clients can call public methods + return VoidVpnService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + + @Override public void onCreate() { super.onCreate(); eipStatus = EipStatus.getInstance(); eipStatus.addObserver(this); - notificationManager = new VpnNotificationManager(this, this); - notificationManager.createVoidVpnNotificationChannel(); + notificationManager = new VpnNotificationManager(this); } @Override @@ -77,6 +93,7 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat thread.run(); } else if (action.equals("android.net.VpnService") && Build.VERSION.SDK_INT >= ALWAYS_ON_MIN_API_LEVEL) { //only always-on feature triggers this + startWithForegroundNotification(); thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); @@ -99,14 +116,19 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat closeFd(); } + @Override + public void onDestroy() { + super.onDestroy(); + notificationManager.cancelAll(); + } + private void stop() { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); - notificationManager.deleteNotificationChannel(NOTIFICATION_CHANNEL_NEWSTATUS_ID); if (thread != null) { thread.interrupt(); } closeFd(); VpnStatus.updateStateString("NOPROCESS", "BLOCKING VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); + stopForeground(true); } public static boolean isRunning() throws NullPointerException { @@ -185,9 +207,11 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat notificationManager.buildVoidVpnNotification( blockingMessage, blockingMessage, - eipStatus.getLevel()); + eipStatus.getLevel(), + this + ); } else { - notificationManager.stopNotifications(NOTIFICATION_CHANNEL_NEWSTATUS_ID); + stopForeground(true); } } @@ -196,9 +220,15 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat startForeground(notificationId, notification); } - @Override - public void onNotificationStop() { - stopForeground(true); + public void startWithForegroundNotification() { + notificationManager.createOpenVpnNotificationChannel(); + String message = getString(R.string.state_disconnected); + notificationManager.buildVoidVpnNotification( + message, + message, + eipStatus.getLevel(), + this::onNotificationBuild + ); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 51069d6d..6fffb403 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -37,6 +37,7 @@ import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4; import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN; import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES; import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS; +import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.OPTIONS; import static se.leap.bitmaskclient.base.models.Constants.PORTS; import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; @@ -70,7 +71,7 @@ public class VpnConfigGenerator { public void checkCapabilities() throws ConfigParser.ConfigParseError { try { - if (apiVersion == 3) { + if (apiVersion >= 3) { JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT); for (int i = 0; i < supportedTransports.length(); i++) { JSONObject transport = supportedTransports.getJSONObject(i); @@ -170,8 +171,13 @@ public class VpnConfigGenerator { gatewayConfigApiv1(stringBuilder, ipAddress, capabilities); break; case 3: + case 4: + String ipAddress6 = gateway.optString(IP_ADDRESS6); + String[] ipAddresses = ipAddress6.isEmpty() ? + new String[]{ipAddress} : + new String[]{ipAddress6, ipAddress}; JSONArray transports = capabilities.getJSONArray(TRANSPORT); - gatewayConfigApiv3(transportType, stringBuilder, ipAddress, transports); + gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses, transports); break; } } catch (JSONException e) { @@ -186,11 +192,11 @@ public class VpnConfigGenerator { return remotes; } - private void gatewayConfigApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void gatewayConfigMinApiv3(Connection.TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { if (transportType == OBFS4) { - obfs4GatewayConfigApiv3(stringBuilder, ipAddress, transports); + obfs4GatewayConfigMinApiv3(stringBuilder, ipAddresses, transports); } else { - ovpnGatewayConfigApi3(stringBuilder, ipAddress, transports); + ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports); } } @@ -209,7 +215,7 @@ public class VpnConfigGenerator { } } - private void ovpnGatewayConfigApi3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { String port; String protocol; JSONObject openvpnTransport = getTransport(transports, OPENVPN); @@ -219,8 +225,10 @@ public class VpnConfigGenerator { JSONArray protocols = openvpnTransport.getJSONArray(PROTOCOLS); for (int k = 0; k < protocols.length(); k++) { protocol = protocols.optString(k); - String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; - stringBuilder.append(newRemote); + for (String ipAddress : ipAddresses) { + String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine; + stringBuilder.append(newRemote); + } } } } @@ -237,8 +245,20 @@ public class VpnConfigGenerator { return selectedTransport; } - private void obfs4GatewayConfigApiv3(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException { + private void obfs4GatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException { JSONObject obfs4Transport = getTransport(transports, OBFS4); + //for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working + // https://community.openvpn.net/openvpn/ticket/1161 + /*for (String ipAddress : ipAddresses) { + String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; + stringBuilder.append(route); + }*/ + + if (ipAddresses.length == 0) { + return; + } + + String ipAddress = ipAddresses[ipAddresses.length - 1]; String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine; stringBuilder.append(route); String remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " " + obfs4Transport.getJSONArray(PROTOCOLS).getString(0) + newLine; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java index b3ed5394..6fac0f72 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnNotificationManager.java @@ -27,32 +27,34 @@ import android.graphics.Color; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.widget.RemoteViews; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + import de.blinkt.openvpn.LaunchVPN; import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.OpenVPNService; -import se.leap.bitmaskclient.base.MainActivity; +import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.MainActivity; import se.leap.bitmaskclient.base.StartActivity; import static android.os.Build.VERSION_CODES.O; -import static androidx.core.app.NotificationCompat.PRIORITY_HIGH; -import static androidx.core.app.NotificationCompat.PRIORITY_MAX; -import static androidx.core.app.NotificationCompat.PRIORITY_MIN; import static android.text.TextUtils.isEmpty; +import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT; +import static androidx.core.app.NotificationCompat.PRIORITY_MAX; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK; import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; +import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; +import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; /** * Created by cyberta on 14.01.18. @@ -60,29 +62,20 @@ import static se.leap.bitmaskclient.base.MainActivity.ACTION_SHOW_VPN_FRAGMENT; public class VpnNotificationManager { + private static final String TAG = VpnNotificationManager.class.getSimpleName(); Context context; - private VpnServiceCallback vpnServiceCallback; - private NotificationManager notificationManager; - private NotificationManagerCompat compatNotificationManager; - private String[] notificationChannels = { - OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID}; - private String lastNotificationChannel = ""; + private final NotificationManagerCompat compatNotificationManager; public interface VpnServiceCallback { void onNotificationBuild(int notificationId, Notification notification); - void onNotificationStop(); } - public VpnNotificationManager(@NonNull Context context, @NonNull VpnServiceCallback vpnServiceCallback) { + public VpnNotificationManager(@NonNull Context context) { this.context = context; - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); compatNotificationManager = NotificationManagerCompat.from(context); - this.vpnServiceCallback = vpnServiceCallback; } - public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status) { + public void buildVoidVpnNotification(final String msg, String tickerText, ConnectionStatus status, VpnServiceCallback callback) { //TODO: implement extra Dashboard.ACTION_ASK_TO_CANCEL_BLOCKING_VPN NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(R.drawable.ic_menu_close_clear_cancel, context.getString(R.string.vpn_button_turn_off_blocking), getStopVoidVpnIntent()); @@ -97,28 +90,45 @@ public class VpnNotificationManager { PRIORITY_MAX, 0, getMainActivityIntent(), - actionBuilder.build()); + actionBuilder.build(), + callback + ); } - public void stopNotifications(String notificationChannelNewstatusId) { - vpnServiceCallback.onNotificationStop(); - compatNotificationManager.cancel(notificationChannelNewstatusId.hashCode()); - } - public void deleteNotificationChannel(String notificationChannel) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - notificationManager.getNotificationChannel(notificationChannel) != null) { - notificationManager.deleteNotificationChannel(notificationChannel); + public void buildForegroundServiceNotification(ConnectionStatus connectionStatus, + VpnServiceCallback callback) { + String message = ""; + // if the app was killed by the system getLastCleanLogMessage returns an empty string + // because the state doesn't get persisted. We can use LEVEL_NOTCONNECTED as an indicator for + // that case because the openvpn service won't be connected then + if (connectionStatus == ConnectionStatus.LEVEL_NOTCONNECTED) { + message = context.getString(R.string.eip_state_not_connected); + } else { + message = VpnStatus.getLastCleanLogMessage(context); } + + NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(0, + context.getString(R.string.vpn_button_turn_on), getStartOpenvpnIntent()); + + buildVpnNotification( + "", + message, + null, + "", + connectionStatus, + OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + PRIORITY_DEFAULT, + 0, + getMainActivityIntent(), + actionBuilder.build(), + callback + ); } - /** - * @param msg - * @param tickerText - * @param status - * @param when - */ - public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, + String tickerText, ConnectionStatus status, long when, + String notificationChannelNewstatusId, VpnServiceCallback vpnServiceCallback) { String cancelString; CharSequence bigmessage = null; String ghostIcon = new String(Character.toChars(0x1f309)); @@ -158,7 +168,7 @@ public class VpnNotificationManager { String appName = context.getString(R.string.app_name); if (isEmpty(profileName)) { title = appName; - } else { + } else { title = context.getString(R.string.notifcation_title_bitmask, appName, profileName); } @@ -167,15 +177,6 @@ public class VpnNotificationManager { contentIntent = getUserInputIntent(msg); else contentIntent = getMainActivityIntent(); - - int priority; - if (OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID.equals(notificationChannelNewstatusId)) { - priority = PRIORITY_HIGH; - } else { - // background channel - priority = PRIORITY_MIN; - } - buildVpnNotification( title, msg, @@ -183,10 +184,19 @@ public class VpnNotificationManager { tickerText, status, notificationChannelNewstatusId, - priority, + PRIORITY_DEFAULT, when, contentIntent, - actionBuilder.build()); + actionBuilder.build(), + vpnServiceCallback); + } + + public void buildOpenVpnNotification(String profileName, boolean isObfuscated, String msg, String tickerText, ConnectionStatus status, long when, String notificationChannelNewstatusId) { + buildOpenVpnNotification(profileName, isObfuscated, msg, tickerText, status, when, notificationChannelNewstatusId, null); + } + + public void cancelAll() { + compatNotificationManager.cancelAll(); } @@ -197,16 +207,19 @@ public class VpnNotificationManager { } // Connection status change messages - CharSequence name = context.getString(R.string.channel_name_status); - NotificationChannel mChannel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(VoidVpnService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } @TargetApi(O) @@ -215,29 +228,20 @@ public class VpnNotificationManager { return; } - // Background message - CharSequence name = context.getString(R.string.channel_name_background); - NotificationChannel mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_BG_ID, - name, NotificationManager.IMPORTANCE_MIN); - - mChannel.setDescription(context.getString(R.string.channel_description_background)); - mChannel.enableLights(false); - - mChannel.setLightColor(Color.DKGRAY); - notificationManager.createNotificationChannel(mChannel); - // Connection status change messages - name = context.getString(R.string.channel_name_status); - mChannel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, - name, NotificationManager.IMPORTANCE_DEFAULT); - - - mChannel.setDescription(context.getString(R.string.channel_description_status)); - mChannel.enableLights(true); - - mChannel.setLightColor(Color.BLUE); - mChannel.setSound(null, null); - notificationManager.createNotificationChannel(mChannel); + NotificationChannel channel = compatNotificationManager.getNotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID); + if (channel == null) { + CharSequence name = context.getString(R.string.channel_name_status); + channel = new NotificationChannel(OpenVPNService.NOTIFICATION_CHANNEL_NEWSTATUS_ID, + name, NotificationManager.IMPORTANCE_DEFAULT); + + channel.setDescription(context.getString(R.string.channel_description_status)); + channel.enableLights(true); + + channel.setLightColor(Color.BLUE); + channel.setSound(null, null); + compatNotificationManager.createNotificationChannel(channel); + } } /** @@ -253,7 +257,9 @@ public class VpnNotificationManager { return remoteViews; } - private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, ConnectionStatus status, String notificationChannelNewstatusId, int priority, long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction) { + private void buildVpnNotification(String title, String message, CharSequence bigMessage, String tickerText, + ConnectionStatus status, String notificationChannelNewstatusId, int priority, + long when, PendingIntent contentIntent, NotificationCompat.Action notificationAction, VpnServiceCallback vpnServiceCallback) { NotificationCompat.Builder nCompatBuilder = new NotificationCompat.Builder(context, notificationChannelNewstatusId); int icon = getIconByConnectionStatus(status); @@ -291,16 +297,10 @@ public class VpnNotificationManager { Notification notification = nCompatBuilder.build(); int notificationId = notificationChannelNewstatusId.hashCode(); - if (!notificationChannelNewstatusId.equals(lastNotificationChannel)) { - // Cancel old notification - for (String channel : notificationChannels) { - stopNotifications(channel); - } - } - compatNotificationManager.notify(notificationId, notification); - vpnServiceCallback.onNotificationBuild(notificationId, notification); - lastNotificationChannel = notificationChannelNewstatusId; + if (vpnServiceCallback != null) { + vpnServiceCallback.onNotificationBuild(notificationId, notification); + } } private PendingIntent getMainActivityIntent() { @@ -308,6 +308,12 @@ public class VpnNotificationManager { return PendingIntent.getActivity(context, 0, startActivity, PendingIntent.FLAG_CANCEL_CURRENT); } + private PendingIntent getStartOpenvpnIntent() { + Intent startIntent = new Intent(context, EIP.class); + startIntent.setAction(EIP_ACTION_START); + return PendingIntent.getService(context, 0, startIntent, PendingIntent.FLAG_CANCEL_CURRENT); + } + private PendingIntent getStopVoidVpnIntent() { Intent stopVoidVpnIntent = new Intent (context, VoidVpnService.class); stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); |