@ -6,6 +6,7 @@ import android.Manifest
import android.app.Activity
import android.app.Application
import android.app.Fragment
import android.app.Notification
import android.app.NotificationChannel
import android.app.PendingIntent
import android.content.Context
@ -50,8 +51,6 @@ class App : UninitializedApp(), libtailscale.AppContext {
val applicationScope = CoroutineScope ( SupervisorJob ( ) + Dispatchers . Default )
companion object {
const val STATUS _CHANNEL _ID = " tailscale-status "
const val STATUS _NOTIFICATION _ID = 1
private const val PEER _TAG = " peer "
private const val FILE _CHANNEL _ID = " tailscale-files "
private const val TAG = " App "
@ -93,6 +92,10 @@ class App : UninitializedApp(), libtailscale.AppContext {
override fun onCreate ( ) {
super . onCreate ( )
createNotificationChannel (
STATUS _CHANNEL _ID , " VPN Status " , NotificationManagerCompat . IMPORTANCE _LOW )
createNotificationChannel (
FILE _CHANNEL _ID , " File transfers " , NotificationManagerCompat . IMPORTANCE _DEFAULT )
appInstance = this
setUnprotectedInstance ( this )
}
@ -125,10 +128,6 @@ class App : UninitializedApp(), libtailscale.AppContext {
Notifier . start ( applicationScope )
connectivityManager = this . getSystemService ( Context . CONNECTIVITY _SERVICE ) as ConnectivityManager
setAndRegisterNetworkCallbacks ( )
createNotificationChannel (
STATUS _CHANNEL _ID , " VPN Status " , NotificationManagerCompat . IMPORTANCE _LOW )
createNotificationChannel (
FILE _CHANNEL _ID , " File transfers " , NotificationManagerCompat . IMPORTANCE _DEFAULT )
applicationScope . launch {
Notifier . state . collect { state ->
val ableToStartVPN = state > Ipn . State . NeedsMachineAuth
@ -221,20 +220,7 @@ class App : UninitializedApp(), libtailscale.AppContext {
setAbleToStartVPN ( ableToStartVPN )
QuickToggleService . updateTile ( )
Log . d ( " App " , " Set Tile Ready: $ableToStartVPN " )
val action = if ( ableToStartVPN ) IPNService . ACTION _STOP _VPN else IPNService . ACTION _START _VPN
val intent = Intent ( this , IPNService :: class . java ) . apply { this . action = action }
val pendingIntent : PendingIntent =
PendingIntent . getBroadcast (
this , 0 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
val notificationMessage =
if ( vpnRunning ) getString ( R . string . connected ) else getString ( R . string . not _connected )
notify (
" Tailscale " ,
notificationMessage ,
vpnRunning ,
STATUS _CHANNEL _ID ,
pendingIntent ,
STATUS _NOTIFICATION _ID )
notifyStatus ( vpnRunning )
}
override fun getModelName ( ) : String {
@ -362,6 +348,9 @@ class App : UninitializedApp(), libtailscale.AppContext {
* /
open class UninitializedApp : Application ( ) {
companion object {
const val STATUS _NOTIFICATION _ID = 1
const val STATUS _CHANNEL _ID = " tailscale-status "
// Key for shared preference that tracks whether or not we're able to start
// the VPN (i.e. we're logged in and machine is authorized).
private const val ABLE _TO _START _VPN _KEY = " ableToStartVPN "
@ -396,7 +385,7 @@ open class UninitializedApp : Application() {
fun startVPN ( ) {
val intent = Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _START _VPN }
start Service( intent )
start Foreground Service( intent )
}
fun stopVPN ( ) {
@ -410,26 +399,7 @@ open class UninitializedApp : Application() {
nm . createNotificationChannel ( channel )
}
fun notify (
title : String ? ,
message : String ? ,
usesEnabledIcon : Boolean ,
channel : String ,
intent : PendingIntent ? ,
notificationID : Int
) {
val icon =
if ( usesEnabledIcon ) R . drawable . ic _notification else R . drawable . ic _notification _disabled
val builder : NotificationCompat . Builder =
NotificationCompat . Builder ( this , channel )
. setSmallIcon ( icon )
. setContentTitle ( title )
. setContentText ( message )
. setContentIntent ( intent )
. setAutoCancel ( true )
. setOnlyAlertOnce ( true )
. setPriority ( NotificationCompat . PRIORITY _DEFAULT )
val nm : NotificationManagerCompat = NotificationManagerCompat . from ( this )
protected fun notifyStatus ( vpnRunning : Boolean ) {
if ( ActivityCompat . checkSelfPermission ( this , Manifest . permission . POST _NOTIFICATIONS ) !=
PackageManager . PERMISSION _GRANTED ) {
// TODO: Consider calling
@ -441,6 +411,30 @@ open class UninitializedApp : Application() {
// for ActivityCompat#requestPermissions for more details.
return
}
nm . notify ( notificationID , builder . build ( ) )
val nm : NotificationManagerCompat = NotificationManagerCompat . from ( this )
nm . notify ( STATUS _NOTIFICATION _ID , buildStatusNotification ( vpnRunning ) )
}
fun buildStatusNotification ( vpnRunning : Boolean ) : Notification {
val message = getString ( if ( vpnRunning ) R . string . connected else R . string . not _connected )
val icon = if ( vpnRunning ) R . drawable . ic _notification else R . drawable . ic _notification _disabled
val action =
if ( vpnRunning ) IPNReceiver . INTENT _DISCONNECT _VPN else IPNReceiver . INTENT _CONNECT _VPN
val actionLabel = getString ( if ( vpnRunning ) R . string . disconnect else R . string . connect )
val intent = Intent ( this , IPNReceiver :: class . java ) . apply { this . action = action }
val pendingIntent : PendingIntent =
PendingIntent . getBroadcast (
this , 0 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
return NotificationCompat . Builder ( this , STATUS _CHANNEL _ID )
. setSmallIcon ( icon )
. setContentTitle ( " Tailscale " )
. setContentText ( message )
. setAutoCancel ( ! vpnRunning )
. setOnlyAlertOnce ( ! vpnRunning )
. setOngoing ( vpnRunning )
. setSilent ( true )
. setPriority ( NotificationCompat . PRIORITY _DEFAULT )
. addAction ( NotificationCompat . Action . Builder ( 0 , actionLabel , pendingIntent ) . build ( ) )
. build ( )
}
}