From 7b7f7254badbdc338ba1f9546042e36162b836f2 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Thu, 21 Mar 2024 14:23:27 -0400 Subject: [PATCH] android: make intent optional in onStartCommand (#229) Updates tailscale/corp#18202 Kotlin requires a nullable optional here. The rest of this is ktfmt Signed-off-by: Jonathan Nobels --- .../main/java/com/tailscale/ipn/IPNService.kt | 225 +++++++++--------- 1 file changed, 116 insertions(+), 109 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/IPNService.kt b/android/src/main/java/com/tailscale/ipn/IPNService.kt index 1a33df3..3558286 100644 --- a/android/src/main/java/com/tailscale/ipn/IPNService.kt +++ b/android/src/main/java/com/tailscale/ipn/IPNService.kt @@ -10,121 +10,128 @@ import android.os.Build import android.system.OsConstants import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import libtailscale.Libtailscale import java.util.UUID +import libtailscale.Libtailscale open class IPNService : VpnService(), libtailscale.IPNService { - private val randomID: String = UUID.randomUUID().toString() - override fun id(): String { - return randomID - } + private val randomID: String = UUID.randomUUID().toString() - override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - if (intent != null && ACTION_STOP_VPN == intent.getAction()) { - (getApplicationContext() as App).autoConnect = false - close() - return START_NOT_STICKY - } - val app = getApplicationContext() as App - if (intent != null && "android.net.VpnService" == intent.getAction()) { - // Start VPN and connect to it due to Always-on VPN - val i = Intent(IPNReceiver.INTENT_CONNECT_VPN) - i.setPackage(getPackageName()) - i.setClass(getApplicationContext(), IPNReceiver::class.java) - sendBroadcast(i) - Libtailscale.requestVPN(this) - app.setWantRunning(true) - return START_STICKY - } - Libtailscale.requestVPN(this) - if (app.vpnReady && app.autoConnect) { - app.setWantRunning(true); - } - return START_STICKY - } + override fun id(): String { + return randomID + } - - private fun close() { - stopForeground(true) - Libtailscale.serviceDisconnect(this) + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent != null && ACTION_STOP_VPN == intent.action) { + (applicationContext as App).autoConnect = false + close() + return START_NOT_STICKY } - - override fun onDestroy() { - close() - super.onDestroy() + val app = applicationContext as App + if (intent != null && "android.net.VpnService" == intent.action) { + // Start VPN and connect to it due to Always-on VPN + val i = Intent(IPNReceiver.INTENT_CONNECT_VPN) + i.setPackage(packageName) + i.setClass(applicationContext, IPNReceiver::class.java) + sendBroadcast(i) + Libtailscale.requestVPN(this) + app.setWantRunning(true) + return START_STICKY } - - override fun onRevoke() { - close() - super.onRevoke() - } - - private fun configIntent(): PendingIntent { - return PendingIntent.getActivity(this, 0, Intent(this, IPNActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - } - - private fun disallowApp(b: Builder, name: String) { - try { - b.addDisallowedApplication(name) - } catch (e: PackageManager.NameNotFoundException) { - } - } - - override fun newBuilder(): VPNServiceBuilder { - val b: Builder = Builder() - .setConfigureIntent(configIntent()) - .allowFamily(OsConstants.AF_INET) - .allowFamily(OsConstants.AF_INET6) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) b.setMetered(false) // Inherit the metered status from the underlying networks. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) b.setUnderlyingNetworks(null) // Use all available networks. - - // RCS/Jibe https://github.com/tailscale/tailscale/issues/2322 - disallowApp(b, "com.google.android.apps.messaging") - - // Stadia https://github.com/tailscale/tailscale/issues/3460 - disallowApp(b, "com.google.stadia.android") - - // Android Auto https://github.com/tailscale/tailscale/issues/3828 - disallowApp(b, "com.google.android.projection.gearhead") - - // GoPro https://github.com/tailscale/tailscale/issues/2554 - disallowApp(b, "com.gopro.smarty") - - // Sonos https://github.com/tailscale/tailscale/issues/2548 - disallowApp(b, "com.sonos.acr") - disallowApp(b, "com.sonos.acr2") - - // Google Chromecast https://github.com/tailscale/tailscale/issues/3636 - disallowApp(b, "com.google.android.apps.chromecast.app") - return VPNServiceBuilder(b) - } - - fun notify(title: String?, message: String?) { - val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, App.NOTIFY_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(title) - .setContentText(message) - .setContentIntent(configIntent()) - .setAutoCancel(true) - .setOnlyAlertOnce(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - val nm: NotificationManagerCompat = NotificationManagerCompat.from(this) - nm.notify(App.NOTIFY_NOTIFICATION_ID, builder.build()) - } - - fun updateStatusNotification(title: String?, message: String?) { - val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, App.STATUS_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notification) - .setContentTitle(title) - .setContentText(message) - .setContentIntent(configIntent()) - .setPriority(NotificationCompat.PRIORITY_LOW) - startForeground(App.STATUS_NOTIFICATION_ID, builder.build()) - } - - companion object { - const val ACTION_REQUEST_VPN = "com.tailscale.ipn.REQUEST_VPN" - const val ACTION_STOP_VPN = "com.tailscale.ipn.STOP_VPN" + Libtailscale.requestVPN(this) + if (app.vpnReady && app.autoConnect) { + app.setWantRunning(true) } + return START_STICKY + } + + private fun close() { + stopForeground(true) + Libtailscale.serviceDisconnect(this) + } + + override fun onDestroy() { + close() + super.onDestroy() + } + + override fun onRevoke() { + close() + super.onRevoke() + } + + private fun configIntent(): PendingIntent { + return PendingIntent.getActivity( + this, + 0, + Intent(this, IPNActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + } + + private fun disallowApp(b: Builder, name: String) { + try { + b.addDisallowedApplication(name) + } catch (e: PackageManager.NameNotFoundException) {} + } + + override fun newBuilder(): VPNServiceBuilder { + val b: Builder = + Builder() + .setConfigureIntent(configIntent()) + .allowFamily(OsConstants.AF_INET) + .allowFamily(OsConstants.AF_INET6) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + b.setMetered(false) // Inherit the metered status from the underlying networks. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + b.setUnderlyingNetworks(null) // Use all available networks. + + // RCS/Jibe https://github.com/tailscale/tailscale/issues/2322 + disallowApp(b, "com.google.android.apps.messaging") + + // Stadia https://github.com/tailscale/tailscale/issues/3460 + disallowApp(b, "com.google.stadia.android") + + // Android Auto https://github.com/tailscale/tailscale/issues/3828 + disallowApp(b, "com.google.android.projection.gearhead") + + // GoPro https://github.com/tailscale/tailscale/issues/2554 + disallowApp(b, "com.gopro.smarty") + + // Sonos https://github.com/tailscale/tailscale/issues/2548 + disallowApp(b, "com.sonos.acr") + disallowApp(b, "com.sonos.acr2") + + // Google Chromecast https://github.com/tailscale/tailscale/issues/3636 + disallowApp(b, "com.google.android.apps.chromecast.app") + return VPNServiceBuilder(b) + } + + fun notify(title: String?, message: String?) { + val builder: NotificationCompat.Builder = + NotificationCompat.Builder(this, App.NOTIFY_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(title) + .setContentText(message) + .setContentIntent(configIntent()) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + val nm: NotificationManagerCompat = NotificationManagerCompat.from(this) + nm.notify(App.NOTIFY_NOTIFICATION_ID, builder.build()) + } + + fun updateStatusNotification(title: String?, message: String?) { + val builder: NotificationCompat.Builder = + NotificationCompat.Builder(this, App.STATUS_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification) + .setContentTitle(title) + .setContentText(message) + .setContentIntent(configIntent()) + .setPriority(NotificationCompat.PRIORITY_LOW) + startForeground(App.STATUS_NOTIFICATION_ID, builder.build()) + } + + companion object { + const val ACTION_REQUEST_VPN = "com.tailscale.ipn.REQUEST_VPN" + const val ACTION_STOP_VPN = "com.tailscale.ipn.STOP_VPN" + } }