From: Tobias Brunner Date: Fri, 8 Jun 2018 12:22:52 +0000 (+0200) Subject: android: Add Quick Settings tile to toggle VPN state X-Git-Tag: 5.7.0dr5~20^2~48 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=64b7a6d62222544806ebc6e5c9fefc37272ad891;p=thirdparty%2Fstrongswan.git android: Add Quick Settings tile to toggle VPN state Only if there is no currently active (or previously active) profile does this currently operate on the configured (or stored most recently used) profile. This way it's possible to use a different connection and quickly disable and re-enable it again. When unlocked the profile name is shown, when locked a generic text is used (this detection doesn't seem to work 100% reliably). To disconnect, the user is forced to unlock the device, connecting is possible without, if the credentials are available and no fatal error occurs (it even works with the system credential store, at least on Android 8.1). Note that the tile is not available right after a reboot. It seems that the system has to be unlocked once to activate third-party tiles (will be interesting to see how this works together with Always-on VPN). --- diff --git a/src/frontends/android/app/src/main/AndroidManifest.xml b/src/frontends/android/app/src/main/AndroidManifest.xml index 5d827a34de..6b0db3782b 100644 --- a/src/frontends/android/app/src/main/AndroidManifest.xml +++ b/src/frontends/android/app/src/main/AndroidManifest.xml @@ -36,6 +36,9 @@ + + + + + + + + . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import android.annotation.TargetApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.utils.Constants; + +@TargetApi(Build.VERSION_CODES.N) +public class VpnTileService extends TileService implements VpnStateService.VpnStateListener +{ + private boolean mListening; + private VpnProfileDataSource mDataSource; + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() + { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + if (mListening) + { + mService.registerListener(VpnTileService.this); + updateTile(); + } + } + }; + + @Override + public void onCreate() + { + super.onCreate(); + + Context context = getApplicationContext(); + context.bindService(new Intent(context, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + getApplicationContext().unbindService(mServiceConnection); + } + mDataSource.close(); + } + + @Override + public void onStartListening() + { + super.onStartListening(); + mListening = true; + if (mService != null) + { + mService.registerListener(this); + updateTile(); + } + } + + @Override + public void onStopListening() + { + super.onStopListening(); + mListening = false; + if (mService != null) + { + mService.unregisterListener(this); + } + } + + private VpnProfile getProfile() + { + SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this); + String uuid = pref.getString(Constants.PREF_DEFAULT_VPN_PROFILE, null); + if (uuid == null || uuid.equals(Constants.PREF_DEFAULT_VPN_PROFILE_MRU)) + { + uuid = pref.getString(Constants.PREF_MRU_VPN_PROFILE, null); + } + + return mDataSource.getVpnProfile(uuid); + } + + @Override + public void onClick() + { + if (mService != null) + { + /* we operate on the current/most recently used profile, but fall back to configuration */ + VpnProfile profile = mService.getProfile(); + if (profile == null) + { + profile = getProfile(); + } + + /* open the main activity in case of an error. since the state is still CONNECTING + * there is a popup confirmation dialog if we connect again, disconnect would work + * but doing two operations is not ideal */ + if (mService.getErrorState() == VpnStateService.ErrorState.NO_ERROR) + { + switch (mService.getState()) + { + case CONNECTING: + case CONNECTED: + Runnable disconnect = new Runnable() + { + @Override + public void run() + { + mService.disconnect(); + } + }; + if (isLocked()) + { + unlockAndRun(disconnect); + } + else + { + disconnect.run(); + } + return; + } + if (profile != null) + { + Intent intent = new Intent(this, VpnProfileControlActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(VpnProfileControlActivity.START_PROFILE); + intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString()); + startActivity(intent); + return; + } + } + } + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityAndCollapse(intent); + } + + @Override + public void stateChanged() + { + updateTile(); + } + + private void updateTile() + { + VpnProfile profile = mService.getProfile(); + VpnStateService.State state = mService.getState(); + VpnStateService.ErrorState error = mService.getErrorState(); + + /* same as above, only use the configured profile if we have no active profile */ + if (profile == null) + { + profile = getProfile(); + } + + Tile tile = getQsTile(); + + if (error != VpnStateService.ErrorState.NO_ERROR) + { + tile.setState(Tile.STATE_INACTIVE); + tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_warning)); + tile.setLabel(getString(R.string.tile_connect)); + } + else + { + switch (state) + { + case DISCONNECTING: + case DISABLED: + tile.setState(Tile.STATE_INACTIVE); + tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_disconnected)); + tile.setLabel(getString(R.string.tile_connect)); + break; + case CONNECTING: + tile.setState(Tile.STATE_ACTIVE); + tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification_connecting)); + tile.setLabel(getString(R.string.tile_disconnect)); + break; + case CONNECTED: + tile.setState(Tile.STATE_ACTIVE); + tile.setIcon(Icon.createWithResource(this, R.drawable.ic_notification)); + tile.setLabel(getString(R.string.tile_disconnect)); + break; + } + } + if (profile != null && !isSecure()) + { + tile.setLabel(profile.getName()); + } + tile.updateTile(); + } +} diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png new file mode 100755 index 0000000000..039877b01d Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_disconnected.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png new file mode 100755 index 0000000000..1adedbe106 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_disconnected.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png new file mode 100755 index 0000000000..e5ddb244e5 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_disconnected.png differ diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index 97df885f86..564a6cc2e4 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -194,4 +194,9 @@ Dies trennt die aktuelle VPN Verbindung! Verbinden + + VPN umschalten + VPN verbinden + VPN trennen + diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index e9c52f14a0..8441c0a7a2 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -194,4 +194,9 @@ This will disconnect the active VPN connection! Połącz + + Toggle VPN + Connect VPN + Disconnect VPN + diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 5f16eecc81..20259ef887 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -191,4 +191,9 @@ This will disconnect the active VPN connection! Соединить + + Toggle VPN + Connect VPN + Disconnect VPN + diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 53bcf71640..1fc1c2926c 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -192,4 +192,9 @@ This will disconnect the active VPN connection! Підключити + + Toggle VPN + Connect VPN + Disconnect VPN + diff --git a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml index 72098cc479..8a886c5d81 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rCN/strings.xml @@ -191,4 +191,9 @@ This will disconnect the active VPN connection! 连接 + + Toggle VPN + Connect VPN + Disconnect VPN + diff --git a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml index ba9fdd044a..f2529a519d 100644 --- a/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml +++ b/src/frontends/android/app/src/main/res/values-zh-rTW/strings.xml @@ -191,4 +191,9 @@ This will disconnect the active VPN connection! 連線 + + Toggle VPN + Connect VPN + Disconnect VPN + diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index cb507beae4..584a2d9a2d 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -194,4 +194,9 @@ This will disconnect the active VPN connection! Connect + + Toggle VPN + Connect VPN + Disconnect VPN +