From d5070425a0acefb9a316c52c253f24c29e929554 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 2 Nov 2016 18:26:43 +0100 Subject: [PATCH] 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. --- .../android/logic/CharonVpnService.java | 142 +++++++++++++++--- .../android/logic/VpnStateService.java | 2 +- .../android/ui/VpnStateFragment.java | 3 +- .../res/drawable-hdpi/ic_notification.png | Bin 0 -> 356 bytes .../drawable-hdpi/ic_notification_warning.png | Bin 0 -> 367 bytes .../res/drawable-mdpi/ic_notification.png | Bin 0 -> 291 bytes .../drawable-mdpi/ic_notification_warning.png | Bin 0 -> 297 bytes .../res/drawable-xhdpi/ic_notification.png | Bin 0 -> 441 bytes .../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 0000000000000000000000000000000000000000..d723ee611fde72b4e74421dbe62e221de64ef999 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFtT~NIEGZ*dNa+D>yUwftE}i6b`7N+FL`6GR;`vh-Yd<& z;I`c22a9Iwu~f4vK2Yj2=r-sv(R9>4)9ydBB<+*bEt7<`b9+25H;8-+~f{9gXjHzgpohvOqSw&RyNsl=JD%Q?(5>cE4@A{WC5HCu^J! z70i~8+W6zu&cz@0lZ0j27~*UtKB4 y@qA&v+ysaFimX8jFtotl=1|kR35%yQFf;rRy{D3ty)*|HYz&^RelF{r5}E*+VUHF7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..05198c8102289db3b2090ff80026d5f93ce3cf75 GIT binary patch literal 367 zcmV-#0g(QQP)cu;+OWZ|$fzSGB0n3ciy)WJe#`6wvh9dz z$stzrfeRixU+;165&|G+X7qlJY1g+GAJ33U*$1oN8*36EXod`Y>5@8%3SMneb z?nCdqAc5{76=dxN&>2!eD(G9!vR?n2x@3P_7`QYU})&{ zi&HX>?@0c?$87lvvI=G$>`?DG()hzjpxf!Ee?x`+l(!s5i~_!8D_moJG<^wMp!p>4 zbVft%Niy0YJZ!s8axStCIQzX}25YwscgQ>Y5M58P4N5!n)a2aHxC9;V^5qro`|#W? zss8w6j{T1>Fthvcn6Q~_Z(!|R&#?68gd;yD9J$E2$;whetYMS<0hWuO8MmI|Jr>CA nrYo^E;R+L%&H^h24g&_Ef@5~etRi;-J;dPY>gTe~DWM4fsbFlb literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f6cd212beb281d04028ee07277b1f9c4320867b4 GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XPJ6mIhE&{2`t$$4J+m+42_~ipo{rrOKg`QYU})&{ zi&HX>?@0c?$87lvvI=G$>`?DG()hzjpxf!Ee?x`+l(!s5i~_!8D_moJG<^wMp!p>4 zbVft%Niy0YJZ!s8axStCIQzX}25YwscgQ>Y5M58P4N5!n)a2aHxC9;V^5qro`|#W? zss8w6j{T1>Fthvcn6Q~_Z(!|R&#?68gd;yD9J$E2$;whetYMS<0hWuO8MmI|J?5Cr tsNDI=O;@5xtY_Jx#0isE@-Z;+FfblHYp4D5t25AB44$rjF6*2UngD~KaM1t& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9961c0ae5af352c8cfbc91356b98196e5c5abefe GIT binary patch literal 441 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@jM1Jhjv*Dd-pnxcKja{CJo-YbNm*ga6@SHMwHAvV2Nre5 zcOT)?)A8A1VKL)@dBQOZ3k!n{7f)3D)2|fX8Mov0*On*$C*3dF`~6+rmh~~Sw3bdv z?h7k=A{exQH}9Z?&H*-!MCIh!9 zC8o1>FvlJAJ=>nZ_wGRc8#xW89J`59t@)eY%Stpw9^Gm>qoMqRa&qhi_WlQZl=4~P zl1vJ@YrzD45=kXH9?%ULH1fN@4x6wqifX$Eh@qIa_WiqkW|NA#zFbm@I zUo5hKft63kPv`nN-sG82@9!*$xxMvT`|HAA7wp29ykX=DXkY><(QrUwHYOzg-Z1qO fpG${5BO8NaoyAE3hqHyixM1*f^>bP0l+XkK5-GS0 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1b5be812f133f283a1e4a1d29484b22a7c91516d GIT binary patch literal 451 zcmV;!0X+VRP)viL)D6p0vz5Re%r{sf6djznViEwf#ZOA}uj zeD@%?$dOCTzV+)qh4lDygMa1lB1|zC`e3P3Ko^`E{96YBrWl<&{foQMPoNmyMk#;- zDB#HKq~zJa;7dxr&bj5>wz*zyuhYl&yCP-}P(a`&_F1!vJ3s*xKmim$0ZsdmDeeGE th<72D5KkeN5C#0_CXTrS0qj?R0RRu&JkLT!G>!lO002ovPDHLkV1lHL!Ug~U literal 0 HcmV?d00001