From d5070425a0acefb9a316c52c253f24c29e929554 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Nov 2016 18:26:43 +0100 Subject: android: Display a permanent notification while connected This forces the service to run in the foreground, meaning the system won't kill it when low on memory. --- .../strongswan/android/logic/CharonVpnService.java | 142 +++++++++++++++++---- .../strongswan/android/logic/VpnStateService.java | 2 +- .../strongswan/android/ui/VpnStateFragment.java | 3 +- .../src/main/res/drawable-hdpi/ic_notification.png | Bin 0 -> 356 bytes .../res/drawable-hdpi/ic_notification_warning.png | Bin 0 -> 367 bytes .../src/main/res/drawable-mdpi/ic_notification.png | Bin 0 -> 291 bytes .../res/drawable-mdpi/ic_notification_warning.png | Bin 0 -> 297 bytes .../main/res/drawable-xhdpi/ic_notification.png | Bin 0 -> 441 bytes .../res/drawable-xhdpi/ic_notification_warning.png | Bin 0 -> 451 bytes 9 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png create mode 100644 src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png create mode 100644 src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png create mode 100644 src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png create mode 100644 src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png create mode 100644 src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java index a6b9fc52d..bf710f03b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java @@ -17,29 +17,9 @@ package org.strongswan.android.logic; -import java.io.File; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.PrivateKey; -import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.data.VpnType.VpnTypeFeature; -import org.strongswan.android.logic.VpnStateService.ErrorState; -import org.strongswan.android.logic.VpnStateService.State; -import org.strongswan.android.logic.imc.ImcState; -import org.strongswan.android.logic.imc.RemediationInstruction; -import org.strongswan.android.ui.MainActivity; -import org.strongswan.android.utils.SettingsWriter; - import android.annotation.TargetApi; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; @@ -53,13 +33,39 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.security.KeyChain; import android.security.KeyChainException; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; import android.system.OsConstants; import android.util.Log; -public class CharonVpnService extends VpnService implements Runnable +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.data.VpnType.VpnTypeFeature; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.logic.imc.ImcState; +import org.strongswan.android.logic.imc.RemediationInstruction; +import org.strongswan.android.ui.MainActivity; +import org.strongswan.android.utils.SettingsWriter; + +import java.io.File; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.PrivateKey; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener { private static final String TAG = CharonVpnService.class.getSimpleName(); public static final String LOG_FILE = "charon.log"; + public static final int VPN_STATE_NOTIFICATION_ID = 1; private String mLogFile; private VpnProfileDataSource mDataSource; @@ -71,6 +77,7 @@ public class CharonVpnService extends VpnService implements Runnable private volatile boolean mProfileUpdated; private volatile boolean mTerminate; private volatile boolean mIsDisconnecting; + private volatile boolean mShowNotification; private VpnStateService mService; private final Object mServiceLock = new Object(); private final ServiceConnection mServiceConnection = new ServiceConnection() { @@ -91,6 +98,7 @@ public class CharonVpnService extends VpnService implements Runnable mService = ((VpnStateService.LocalBinder)service).getService(); } /* we are now ready to start the handler thread */ + mService.registerListener(CharonVpnService.this); mConnectionHandler.start(); } }; @@ -163,6 +171,7 @@ public class CharonVpnService extends VpnService implements Runnable } if (mService != null) { + mService.unregisterListener(this); unbindService(mServiceConnection); } mDataSource.close(); @@ -220,6 +229,7 @@ public class CharonVpnService extends VpnService implements Runnable startConnection(mCurrentProfile); mIsDisconnecting = false; + addNotification(); BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName(), mCurrentProfile.getSplitTunneling()); if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD))) { @@ -268,10 +278,94 @@ public class CharonVpnService extends VpnService implements Runnable deinitializeCharon(); Log.i(TAG, "charon stopped"); mCurrentProfile = null; + removeNotification(); } } } + /** + * Add a permanent notification while we are connected to avoid the service getting killed by + * the system when low on memory. + */ + private void addNotification() + { + mShowNotification = true; + startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification()); + } + + /** + * Remove the permanent notification. + */ + private void removeNotification() + { + mShowNotification = false; + stopForeground(true); + } + + + /** + * Build a notification matching the current state + */ + private Notification buildNotification() + { + VpnProfile profile = mService.getProfile(); + State state = mService.getState(); + ErrorState error = mService.getErrorState(); + String name = ""; + + if (profile != null) + { + name = profile.getName(); + } + android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this) + .setContentText(name) + .setSmallIcon(R.drawable.ic_notification) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setVisibility(NotificationCompat.VISIBILITY_SECRET); + int s = R.string.state_disabled; + if (error != ErrorState.NO_ERROR) + { + s = R.string.state_error; + builder.setSmallIcon(R.drawable.ic_notification_warning); + builder.setColor(ContextCompat.getColor(this, R.color.error_text)); + } + else + { + switch (state) + { + case CONNECTING: + s = R.string.state_connecting; + builder.setSmallIcon(R.drawable.ic_notification_warning); + builder.setColor(ContextCompat.getColor(this, R.color.warning_text)); + break; + case CONNECTED: + s = R.string.state_connected; + builder.setColor(ContextCompat.getColor(this, R.color.success_text)); + builder.setUsesChronometer(true); + break; + case DISCONNECTING: + s = R.string.state_disconnecting; + break; + } + } + builder.setContentTitle(getString(s)); + + Intent intent = new Intent(getApplicationContext(), MainActivity.class); + PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + builder.setContentIntent(pending); + return builder.build(); + } + + @Override + public void stateChanged() { + if (mShowNotification) + { + NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification()); + } + } + /** * Notify the state service about a new connection attempt. * Called by the handler thread. @@ -521,7 +615,7 @@ public class CharonVpnService extends VpnService implements Runnable * * @param builder BuilderAdapter for this connection * @param logfile absolute path to the logfile - * @param boyd enable BYOD features + * @param byod enable BYOD features * @return TRUE if initialization was successful */ public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod); diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java index 7b40e942f..e35277d8c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/logic/VpnStateService.java @@ -315,7 +315,7 @@ public class VpnStateService extends Service * * May be called from threads other than the main thread. * - * @param error error state + * @param state IMC state */ public void setImcState(final ImcState state) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java index 0b093d78f..1ea01515c 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java @@ -187,12 +187,11 @@ public class VpnStateFragment extends Fragment implements VpnStateListener State state = mService.getState(); ErrorState error = mService.getErrorState(); ImcState imcState = mService.getImcState(); - String name = "", gateway = ""; + String name = ""; if (profile != null) { name = profile.getName(); - gateway = profile.getGateway(); } if (reportError(connectionID, name, error, imcState)) diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png new file mode 100644 index 000000000..d723ee611 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png new file mode 100644 index 000000000..05198c810 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-hdpi/ic_notification_warning.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png new file mode 100644 index 000000000..fa5d642c5 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png new file mode 100644 index 000000000..f6cd212be Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-mdpi/ic_notification_warning.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png new file mode 100644 index 000000000..9961c0ae5 Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ diff --git a/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png new file mode 100644 index 000000000..1b5be812f Binary files /dev/null and b/src/frontends/android/app/src/main/res/drawable-xhdpi/ic_notification_warning.png differ -- cgit v1.2.3