@ -10,121 +10,128 @@ import android.os.Build
import android.system.OsConstants
import android.system.OsConstants
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.NotificationManagerCompat
import libtailscale.Libtailscale
import java.util.UUID
import java.util.UUID
import libtailscale.Libtailscale
open class IPNService : VpnService ( ) , libtailscale . IPNService {
open class IPNService : VpnService ( ) , libtailscale . IPNService {
private val randomID : String = UUID . randomUUID ( ) . toString ( )
private val randomID : String = UUID . randomUUID ( ) . toString ( )
override fun id ( ) : String {
return randomID
}
override fun onStartCommand ( intent : Intent , flags : Int , startId : Int ) : Int {
override fun id ( ) : String {
if ( intent != null && ACTION _STOP _VPN == intent . getAction ( ) ) {
return randomID
( 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 onStartCommand ( intent : Intent ? , flags : Int , startId : Int ) : Int {
private fun close ( ) {
if ( intent != null && ACTION _STOP _VPN == intent . action ) {
stopForeground ( true )
( applicationContext as App ) . autoConnect = false
Libtailscale . serviceDisconnect ( this )
close ( )
return START _NOT _STICKY
}
}
val app = applicationContext as App
override fun onDestroy ( ) {
if ( intent != null && " android.net.VpnService " == intent . action ) {
close ( )
// Start VPN and connect to it due to Always-on VPN
super . onDestroy ( )
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
}
}
Libtailscale . requestVPN ( this )
override fun onRevoke ( ) {
if ( app . vpnReady && app . autoConnect ) {
close ( )
app . setWantRunning ( true )
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 "
}
}
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 "
}
}
}