44
55package org.openziti.mobile
66
7+ import android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED
8+ import android.app.Notification
9+ import android.app.NotificationChannel
10+ import android.app.NotificationManager
711import android.app.PendingIntent
812import android.content.Intent
13+ import android.content.pm.ServiceInfo
914import android.net.ConnectivityManager
1015import android.net.LinkProperties
1116import android.net.Network
1217import android.net.NetworkCapabilities
1318import android.net.NetworkRequest
1419import android.net.VpnService
1520import android.os.Binder
21+ import android.os.Build
1622import android.os.IBinder
1723import android.system.OsConstants
24+ import androidx.core.app.NotificationCompat
1825import kotlinx.coroutines.CoroutineScope
1926import kotlinx.coroutines.Job
2027import kotlinx.coroutines.SupervisorJob
@@ -140,6 +147,11 @@ class ZitiVPNService : VpnService(), CoroutineScope {
140147 }
141148 }
142149
150+ override fun onTimeout (startId : Int , fgsType : Int ) {
151+ super .onTimeout(startId, fgsType)
152+ Log .i(" onTimeout($startId , $fgsType )" )
153+ }
154+
143155 override fun onCreate () {
144156 Log .i(" onCreate()" )
145157 ZitiMobileEdgeApp .vpnService = this
@@ -153,6 +165,10 @@ class ZitiVPNService : VpnService(), CoroutineScope {
153165 connMgr.registerNetworkCallback(netReq, networkMonitor)
154166 connMgr.addDefaultNetworkActiveListener(netListener)
155167
168+ runCatching { markForeground() }.onFailure { it
169+ Log .w(it, " failed to mark service foreground" )
170+ }
171+
156172 monitor = launch {
157173 launch {
158174 Log .i(" monitoring route updates" )
@@ -189,13 +205,13 @@ class ZitiVPNService : VpnService(), CoroutineScope {
189205 }.onSuccess {
190206 Log .i(" tunnel $cmd success" )
191207 }.onFailure {
192- Log .w(" exception during tunnel $cmd " , it )
208+ Log .w(it, " exception during tunnel $cmd " )
193209 }
194210 }
195211 }
196212
197213 monitor.invokeOnCompletion {
198- Log .i(" monitor stopped" , it )
214+ Log .i(it, " monitor stopped" )
199215 }
200216 }
201217
@@ -216,15 +232,13 @@ class ZitiVPNService : VpnService(), CoroutineScope {
216232 Log .i(" monitor=$monitor " )
217233 val action = intent?.action
218234 when (action) {
219- SERVICE_INTERFACE ,
220- START -> tunnelState.value = " start"
221-
222- RESTART -> tunnelState.value = " restart"
223-
224- STOP -> tunnelState.value = " stop"
225-
226- else -> Log .wtf(" what is your intent? $intent " )
235+ STOP -> {
236+ tunnelState.value = STOP
237+ return START_NOT_STICKY
238+ }
227239
240+ RESTART -> tunnelState.value = RESTART
241+ else -> tunnelState.value = START
228242 }
229243 return START_STICKY
230244 }
@@ -244,7 +258,9 @@ class ZitiVPNService : VpnService(), CoroutineScope {
244258 allowFamily(OsConstants .AF_INET )
245259 allowFamily(OsConstants .AF_INET6 )
246260 allowBypass()
247- setMetered(metered)
261+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
262+ setMetered(metered)
263+ }
248264
249265 val range = runBlocking { model.zitiRange.first().toRoute() }
250266 val size = if (range.address is Inet6Address ) 128 else 32
@@ -256,7 +272,7 @@ class ZitiVPNService : VpnService(), CoroutineScope {
256272 route.runCatching {
257273 addRoute(route.address, route.bits)
258274 }.onFailure {
259- Log .e(" invalid route[$route ]" , it )
275+ Log .e(it, " invalid route[$route ]" )
260276 }
261277 }
262278 setUnderlyingNetworks(null )
@@ -302,4 +318,36 @@ class ZitiVPNService : VpnService(), CoroutineScope {
302318 fun isVPNActive () = tun.isActive()
303319 fun getUptime (): Duration = tun.getUptime().toJavaDuration()
304320 }
321+
322+ private fun markForeground () {
323+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .Q ) {
324+ startForeground(1 , createNotification())
325+ } else {
326+ startForeground(1 , createNotification(), ServiceInfo .FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED )
327+ }
328+ }
329+
330+ private fun createNotification (): Notification {
331+ val channelId = " Ziti Mobile Edge"
332+ val channel = NotificationChannel (
333+ channelId,
334+ " Firewall Status" ,
335+ NotificationManager .IMPORTANCE_DEFAULT
336+ )
337+ val manager = getSystemService(NotificationManager ::class .java)
338+ manager.createNotificationChannel(channel)
339+
340+ val pendingIntent = PendingIntent .getActivity(
341+ this , 0 , Intent (this , ZitiMobileEdgeActivity ::class .java),
342+ PendingIntent .FLAG_IMMUTABLE or PendingIntent .FLAG_UPDATE_CURRENT
343+ )
344+
345+ return NotificationCompat .Builder (this , channelId)
346+ .setContentTitle(" Ziti Mobile Edge" )
347+ .setContentText(" Ziti is active" )
348+ .setSmallIcon(R .drawable.z)
349+ .setContentIntent(pendingIntent)
350+ .build()
351+ }
352+
305353}
0 commit comments