@ -1,7 +1,6 @@
// Copyright (c) Tailscale Inc & AUTHORS
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn
package com.tailscale.ipn
import android.Manifest
import android.Manifest
import android.app.Application
import android.app.Application
import android.app.Notification
import android.app.Notification
@ -33,8 +32,8 @@ import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.Netmap
import com.tailscale.ipn.ui.model.Netmap
import com.tailscale.ipn.ui.notifier.HealthNotifier
import com.tailscale.ipn.ui.notifier.HealthNotifier
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.viewModel. Vpn ViewModel
import com.tailscale.ipn.ui.viewModel. App ViewModel
import com.tailscale.ipn.ui.viewModel. Vpn ViewModelFactory
import com.tailscale.ipn.ui.viewModel. App ViewModelFactory
import com.tailscale.ipn.util.FeatureFlags
import com.tailscale.ipn.util.FeatureFlags
import com.tailscale.ipn.util.ShareFileHelper
import com.tailscale.ipn.util.ShareFileHelper
import com.tailscale.ipn.util.TSLog
import com.tailscale.ipn.util.TSLog
@ -53,17 +52,14 @@ import java.io.IOException
import java.net.NetworkInterface
import java.net.NetworkInterface
import java.security.GeneralSecurityException
import java.security.GeneralSecurityException
import java.util.Locale
import java.util.Locale
class App : UninitializedApp ( ) , libtailscale . AppContext , ViewModelStoreOwner {
class App : UninitializedApp ( ) , libtailscale . AppContext , ViewModelStoreOwner {
val applicationScope = CoroutineScope ( SupervisorJob ( ) + Dispatchers . Default )
val applicationScope = CoroutineScope ( SupervisorJob ( ) + Dispatchers . Default )
companion object {
companion object {
private const val FILE _CHANNEL _ID = " tailscale-files "
private const val FILE _CHANNEL _ID = " tailscale-files "
// Key to store the SAF URI in EncryptedSharedPreferences.
// Key to store the SAF URI in EncryptedSharedPreferences.
private val PREF _KEY _SAF _URI = " saf_directory_uri "
private val PREF _KEY _SAF _URI = " saf_directory_uri "
private const val TAG = " App "
private const val TAG = " App "
private lateinit var appInstance : App
private lateinit var appInstance : App
/ * *
/ * *
* Initializes the app ( if necessary ) and returns the singleton app instance . Always use this
* Initializes the app ( if necessary ) and returns the singleton app instance . Always use this
* function to obtain an App reference to make sure the app initializes .
* function to obtain an App reference to make sure the app initializes .
@ -74,45 +70,33 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
return appInstance
return appInstance
}
}
}
}
val dns = DnsConfig ( )
val dns = DnsConfig ( )
private lateinit var connectivityManager : ConnectivityManager
private lateinit var connectivityManager : ConnectivityManager
private lateinit var mdmChangeReceiver : MDMSettingsChangedReceiver
private lateinit var mdmChangeReceiver : MDMSettingsChangedReceiver
private lateinit var app : libtailscale . Application
private lateinit var app : libtailscale . Application
override val viewModelStore : ViewModelStore
override val viewModelStore : ViewModelStore
get ( ) = appViewModelStore
get ( ) = appViewModelStore
private val appViewModelStore : ViewModelStore by lazy { ViewModelStore ( ) }
private val appViewModelStore : ViewModelStore by lazy { ViewModelStore ( ) }
var healthNotifier : HealthNotifier ? = null
var healthNotifier : HealthNotifier ? = null
override fun getPlatformDNSConfig ( ) : String = dns . dnsConfigAsString
override fun getPlatformDNSConfig ( ) : String = dns . dnsConfigAsString
override fun getInstallSource ( ) : String = AppSourceChecker . getInstallSource ( this )
override fun getInstallSource ( ) : String = AppSourceChecker . getInstallSource ( this )
override fun shouldUseGoogleDNSFallback ( ) : Boolean = BuildConfig . USE _GOOGLE _DNS _FALLBACK
override fun shouldUseGoogleDNSFallback ( ) : Boolean = BuildConfig . USE _GOOGLE _DNS _FALLBACK
override fun log ( s : String , s1 : String ) {
override fun log ( s : String , s1 : String ) {
Log . d ( s , s1 )
Log . d ( s , s1 )
}
}
fun getLibtailscaleApp ( ) : libtailscale . Application {
fun getLibtailscaleApp ( ) : libtailscale . Application {
if ( !is Initialized ) {
if ( !is Initialized ) {
initOnce ( ) // Calls the synchronized initialization logic
initOnce ( ) // Calls the synchronized initialization logic
}
}
return app
return app
}
}
override fun onCreate ( ) {
override fun onCreate ( ) {
super . onCreate ( )
super . onCreate ( )
appInstance = this
appInstance = this
setUnprotectedInstance ( this )
setUnprotectedInstance ( this )
mdmChangeReceiver = MDMSettingsChangedReceiver ( )
mdmChangeReceiver = MDMSettingsChangedReceiver ( )
val filter = IntentFilter ( Intent . ACTION _APPLICATION _RESTRICTIONS _CHANGED )
val filter = IntentFilter ( Intent . ACTION _APPLICATION _RESTRICTIONS _CHANGED )
registerReceiver ( mdmChangeReceiver , filter )
registerReceiver ( mdmChangeReceiver , filter )
createNotificationChannel (
createNotificationChannel (
STATUS _CHANNEL _ID ,
STATUS _CHANNEL _ID ,
getString ( R . string . vpn _status ) ,
getString ( R . string . vpn _status ) ,
@ -129,7 +113,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
getString ( R . string . health _channel _description ) ,
getString ( R . string . health _channel _description ) ,
NotificationManagerCompat . IMPORTANCE _HIGH )
NotificationManagerCompat . IMPORTANCE _HIGH )
}
}
override fun onTerminate ( ) {
override fun onTerminate ( ) {
super . onTerminate ( )
super . onTerminate ( )
Notifier . stop ( )
Notifier . stop ( )
@ -138,19 +121,15 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
viewModelStore . clear ( )
viewModelStore . clear ( )
unregisterReceiver ( mdmChangeReceiver )
unregisterReceiver ( mdmChangeReceiver )
}
}
@Volatile private var isInitialized = false
@Volatile private var isInitialized = false
@Synchronized
@Synchronized
private fun initOnce ( ) {
private fun initOnce ( ) {
if ( isInitialized ) {
if ( isInitialized ) {
return
return
}
}
initializeApp ( )
initializeApp ( )
isInitialized = true
isInitialized = true
}
}
private fun initializeApp ( ) {
private fun initializeApp ( ) {
// Check if a directory URI has already been stored.
// Check if a directory URI has already been stored.
val storedUri = getStoredDirectoryUri ( )
val storedUri = getStoredDirectoryUri ( )
@ -166,7 +145,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
applicationScope . launch {
applicationScope . launch {
val rm = getSystemService ( Context . RESTRICTIONS _SERVICE ) as RestrictionsManager
val rm = getSystemService ( Context . RESTRICTIONS _SERVICE ) as RestrictionsManager
MDMSettings . update ( get ( ) , rm )
MDMSettings . update ( get ( ) , rm )
Notifier . state . collect { _ ->
Notifier . state . collect { _ ->
combine ( Notifier . state , MDMSettings . forceEnabled . flow , Notifier . prefs , Notifier . netmap ) {
combine ( Notifier . state , MDMSettings . forceEnabled . flow , Notifier . prefs , Notifier . netmap ) {
state ,
state ,
@ -184,11 +162,9 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
if ( state == Ipn . State . Stopped ) {
if ( state == Ipn . State . Stopped ) {
notifyStatus ( vpnRunning = false , hideDisconnectAction = hideDisconnectAction . value )
notifyStatus ( vpnRunning = false , hideDisconnectAction = hideDisconnectAction . value )
}
}
val vpnRunning = state == Ipn . State . Starting || state == Ipn . State . Running
val vpnRunning = state == Ipn . State . Starting || state == Ipn . State . Running
updateConnStatus ( ableToStartVPN )
updateConnStatus ( ableToStartVPN )
QuickToggleService . setVPNRunning ( vpnRunning )
QuickToggleService . setVPNRunning ( vpnRunning )
// Update notification status when VPN is running
// Update notification status when VPN is running
if ( vpnRunning ) {
if ( vpnRunning ) {
notifyStatus (
notifyStatus (
@ -205,21 +181,22 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
TSLog . init ( this )
TSLog . init ( this )
FeatureFlags . initialize ( mapOf ( " enable_new_search " to true ) )
FeatureFlags . initialize ( mapOf ( " enable_new_search " to true ) )
}
}
/ * *
/ * *
* Called when a SAF directory URI is available ( either already stored or chosen ) . We must restart
* Called when a SAF directory URI is available ( either already stored or chosen ) . We must restart
* Tailscale because directFileRoot must be set before LocalBackend starts being used .
* Tailscale because directFileRoot must be set before LocalBackend starts being used .
* /
* /
fun startLibtailscale ( directFileRoot : String ) {
fun startLibtailscale ( directFileRoot : String ) {
ShareFileHelper . init ( this , directFileRoot )
app = Libtailscale . start ( this . filesDir . absolutePath , directFileRoot , this )
app = Libtailscale . start ( this . filesDir . absolutePath , directFileRoot , this )
ShareFileHelper . init ( this , app , directFileRoot , applicationScope )
Request . setApp ( app )
Request . setApp ( app )
Notifier . setApp ( app )
Notifier . setApp ( app )
Notifier . start ( applicationScope )
Notifier . start ( applicationScope )
}
}
private fun initViewModels ( ) {
private fun initViewModels ( ) {
vpnViewModel = ViewModelProvider ( this , VpnViewModelFactory ( this ) ) . get ( VpnViewModel :: class . java )
appViewModel =
ViewModelProvider ( this , AppViewModelFactory ( this , ShareFileHelper . observeTaildropPrompt ( ) ) )
. get ( AppViewModel :: class . java )
}
}
fun setWantRunning ( wantRunning : Boolean , onSuccess : ( ( ) -> Unit ) ? = null ) {
fun setWantRunning ( wantRunning : Boolean , onSuccess : ( ( ) -> Unit ) ? = null ) {
@ -233,14 +210,12 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
Client ( applicationScope )
Client ( applicationScope )
. editPrefs ( Ipn . MaskedPrefs ( ) . apply { WantRunning = wantRunning } , callback )
. editPrefs ( Ipn . MaskedPrefs ( ) . apply { WantRunning = wantRunning } , callback )
}
}
// encryptToPref a byte array of data using the Jetpack Security
// encryptToPref a byte array of data using the Jetpack Security
// library and writes it to a global encrypted preference store.
// library and writes it to a global encrypted preference store.
@Throws ( IOException :: class , GeneralSecurityException :: class )
@Throws ( IOException :: class , GeneralSecurityException :: class )
override fun encryptToPref ( prefKey : String ? , plaintext : String ? ) {
override fun encryptToPref ( prefKey : String ? , plaintext : String ? ) {
getEncryptedPrefs ( ) . edit ( ) . putString ( prefKey , plaintext ) . commit ( )
getEncryptedPrefs ( ) . edit ( ) . putString ( prefKey , plaintext ) . commit ( )
}
}
// decryptFromPref decrypts a encrypted preference using the Jetpack Security
// decryptFromPref decrypts a encrypted preference using the Jetpack Security
// library and returns the plaintext.
// library and returns the plaintext.
@Throws ( IOException :: class , GeneralSecurityException :: class )
@Throws ( IOException :: class , GeneralSecurityException :: class )
@ -250,18 +225,18 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
override fun getStateStoreKeysJSON ( ) : String {
override fun getStateStoreKeysJSON ( ) : String {
val prefix = " statestore- "
val prefix = " statestore- "
val keys = getEncryptedPrefs ( )
val keys =
. getAll ( )
getEncryptedPrefs ( )
. keys
. getAll ( )
. filter { it . startsWith ( prefix ) }
. keys
. map { it . removePrefix ( prefix ) }
. filter { it . startsWith ( prefix ) }
. map { it . removePrefix ( prefix ) }
return org . json . JSONArray ( keys ) . toString ( )
return org . json . JSONArray ( keys ) . toString ( )
}
}
@Throws ( IOException :: class , GeneralSecurityException :: class )
@Throws ( IOException :: class , GeneralSecurityException :: class )
fun getEncryptedPrefs ( ) : SharedPreferences {
fun getEncryptedPrefs ( ) : SharedPreferences {
val key = MasterKey . Builder ( this ) . setKeyScheme ( MasterKey . KeyScheme . AES256 _GCM ) . build ( )
val key = MasterKey . Builder ( this ) . setKeyScheme ( MasterKey . KeyScheme . AES256 _GCM ) . build ( )
return EncryptedSharedPreferences . create (
return EncryptedSharedPreferences . create (
this ,
this ,
" secret_shared_prefs " ,
" secret_shared_prefs " ,
@ -269,12 +244,10 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
EncryptedSharedPreferences . PrefKeyEncryptionScheme . AES256 _SIV ,
EncryptedSharedPreferences . PrefKeyEncryptionScheme . AES256 _SIV ,
EncryptedSharedPreferences . PrefValueEncryptionScheme . AES256 _GCM )
EncryptedSharedPreferences . PrefValueEncryptionScheme . AES256 _GCM )
}
}
fun getStoredDirectoryUri ( ) : Uri ? {
fun getStoredDirectoryUri ( ) : Uri ? {
val uriString = getEncryptedPrefs ( ) . getString ( PREF _KEY _SAF _URI , null )
val uriString = getEncryptedPrefs ( ) . getString ( PREF _KEY _SAF _URI , null )
return uriString ?. let { Uri . parse ( it ) }
return uriString ?. let { Uri . parse ( it ) }
}
}
/ *
/ *
* setAbleToStartVPN remembers whether or not we ' re able to start the VPN
* setAbleToStartVPN remembers whether or not we ' re able to start the VPN
* by storing this in a shared preference . This allows us to check this
* by storing this in a shared preference . This allows us to check this
@ -285,7 +258,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
QuickToggleService . updateTile ( )
QuickToggleService . updateTile ( )
TSLog . d ( " App " , " Set Tile Ready: $ableToStartVPN " )
TSLog . d ( " App " , " Set Tile Ready: $ableToStartVPN " )
}
}
override fun getModelName ( ) : String {
override fun getModelName ( ) : String {
val manu = Build . MANUFACTURER
val manu = Build . MANUFACTURER
var model = Build . MODEL
var model = Build . MODEL
@ -296,17 +268,13 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
}
}
return " $manu $model "
return " $manu $model "
}
}
override fun getOSVersion ( ) : String = Build . VERSION . RELEASE
override fun getOSVersion ( ) : String = Build . VERSION . RELEASE
override fun isChromeOS ( ) : Boolean {
override fun isChromeOS ( ) : Boolean {
return packageManager . hasSystemFeature ( " android.hardware.type.pc " )
return packageManager . hasSystemFeature ( " android.hardware.type.pc " )
}
}
override fun getInterfacesAsString ( ) : String {
override fun getInterfacesAsString ( ) : String {
val interfaces : ArrayList < NetworkInterface > =
val interfaces : ArrayList < NetworkInterface > =
java . util . Collections . list ( NetworkInterface . getNetworkInterfaces ( ) )
java . util . Collections . list ( NetworkInterface . getNetworkInterfaces ( ) )
val sb = StringBuilder ( )
val sb = StringBuilder ( )
for ( nif in interfaces ) {
for ( nif in interfaces ) {
try {
try {
@ -322,7 +290,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
nif . isLoopback ,
nif . isLoopback ,
nif . isPointToPoint ,
nif . isPointToPoint ,
nif . supportsMulticast ( ) ) )
nif . supportsMulticast ( ) ) )
for ( ia in nif . interfaceAddresses ) {
for ( ia in nif . interfaceAddresses ) {
val parts = ia . toString ( ) . split ( " / " , limit = 0 )
val parts = ia . toString ( ) . split ( " / " , limit = 0 )
if ( parts . size > 1 ) {
if ( parts . size > 1 ) {
@ -334,16 +301,13 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
}
}
sb . append ( " \n " )
sb . append ( " \n " )
}
}
return sb . toString ( )
return sb . toString ( )
}
}
@Throws (
@Throws (
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
override fun getSyspolicyBooleanValue ( key : String ) : Boolean {
override fun getSyspolicyBooleanValue ( key : String ) : Boolean {
return getSyspolicyStringValue ( key ) == " true "
return getSyspolicyStringValue ( key ) == " true "
}
}
@Throws (
@Throws (
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
override fun getSyspolicyStringValue ( key : String ) : String {
override fun getSyspolicyStringValue ( key : String ) : String {
@ -353,7 +317,6 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
}
}
return setting . value ?. toString ( ) ?: " "
return setting . value ?. toString ( ) ?: " "
}
}
@Throws (
@Throws (
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
IOException :: class , GeneralSecurityException :: class , MDMSettings . NoSuchKeyException :: class )
override fun getSyspolicyStringArrayJSONValue ( key : String ) : String {
override fun getSyspolicyStringArrayJSONValue ( key : String ) : String {
@ -369,12 +332,10 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
throw MDMSettings . NoSuchKeyException ( )
throw MDMSettings . NoSuchKeyException ( )
}
}
}
}
fun notifyPolicyChanged ( ) {
fun notifyPolicyChanged ( ) {
app . notifyPolicyChanged ( )
app . notifyPolicyChanged ( )
}
}
}
}
/ * *
/ * *
* UninitializedApp contains all of the methods of App that can be used without having to initialize
* UninitializedApp contains all of the methods of App that can be used without having to initialize
* the Go backend . This is useful when you want to access functions on the App without creating side
* the Go backend . This is useful when you want to access functions on the App without creating side
@ -383,30 +344,24 @@ class App : UninitializedApp(), libtailscale.AppContext, ViewModelStoreOwner {
open class UninitializedApp : Application ( ) {
open class UninitializedApp : Application ( ) {
companion object {
companion object {
const val TAG = " UninitializedApp "
const val TAG = " UninitializedApp "
const val STATUS _NOTIFICATION _ID = 1
const val STATUS _NOTIFICATION _ID = 1
const val STATUS _EXIT _NODE _FAILURE _NOTIFICATION _ID = 2
const val STATUS _EXIT _NODE _FAILURE _NOTIFICATION _ID = 2
const val STATUS _CHANNEL _ID = " tailscale-status "
const val STATUS _CHANNEL _ID = " tailscale-status "
// Key for shared preference that tracks whether or not we're able to start
// 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).
// the VPN (i.e. we're logged in and machine is authorized).
private const val ABLE _TO _START _VPN _KEY = " ableToStartVPN "
private const val ABLE _TO _START _VPN _KEY = " ableToStartVPN "
private const val DISALLOWED _APPS _KEY = " disallowedApps "
private const val DISALLOWED _APPS _KEY = " disallowedApps "
// File for shared preferences that are not encrypted.
// File for shared preferences that are not encrypted.
private const val UNENCRYPTED _PREFERENCES = " unencrypted "
private const val UNENCRYPTED _PREFERENCES = " unencrypted "
private lateinit var appInstance : UninitializedApp
private lateinit var appInstance : UninitializedApp
lateinit var notificationManager : NotificationManagerCompat
lateinit var notificationManager : NotificationManagerCompat
lateinit var vpnViewModel: Vpn ViewModel
lateinit var appViewModel: App ViewModel
@JvmStatic
@JvmStatic
fun get ( ) : UninitializedApp {
fun get ( ) : UninitializedApp {
return appInstance
return appInstance
}
}
/ * *
/ * *
* Return the name of the active ( but not the selected / prior one ) exit node based on the
* Return the name of the active ( but not the selected / prior one ) exit node based on the
* provided [ Ipn . Prefs ] and [ Netmap . NetworkMap ] .
* provided [ Ipn . Prefs ] and [ Netmap . NetworkMap ] .
@ -419,24 +374,19 @@ open class UninitializedApp : Application() {
}
}
}
}
}
}
protected fun setUnprotectedInstance ( instance : UninitializedApp ) {
protected fun setUnprotectedInstance ( instance : UninitializedApp ) {
appInstance = instance
appInstance = instance
}
}
protected fun setAbleToStartVPN ( rdy : Boolean ) {
protected fun setAbleToStartVPN ( rdy : Boolean ) {
getUnencryptedPrefs ( ) . edit ( ) . putBoolean ( ABLE _TO _START _VPN _KEY , rdy ) . apply ( )
getUnencryptedPrefs ( ) . edit ( ) . putBoolean ( ABLE _TO _START _VPN _KEY , rdy ) . apply ( )
}
}
/** This function can be called without initializing the App. */
/** This function can be called without initializing the App. */
fun isAbleToStartVPN ( ) : Boolean {
fun isAbleToStartVPN ( ) : Boolean {
return getUnencryptedPrefs ( ) . getBoolean ( ABLE _TO _START _VPN _KEY , false )
return getUnencryptedPrefs ( ) . getBoolean ( ABLE _TO _START _VPN _KEY , false )
}
}
private fun getUnencryptedPrefs ( ) : SharedPreferences {
private fun getUnencryptedPrefs ( ) : SharedPreferences {
return getSharedPreferences ( UNENCRYPTED _PREFERENCES , MODE _PRIVATE )
return getSharedPreferences ( UNENCRYPTED _PREFERENCES , MODE _PRIVATE )
}
}
fun startVPN ( ) {
fun startVPN ( ) {
val intent = Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _START _VPN }
val intent = Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _START _VPN }
// FLAG_UPDATE_CURRENT ensures that if the intent is already pending, the existing intent will
// FLAG_UPDATE_CURRENT ensures that if the intent is already pending, the existing intent will
@ -449,7 +399,6 @@ open class UninitializedApp : Application() {
PendingIntent . FLAG _UPDATE _CURRENT or
PendingIntent . FLAG _UPDATE _CURRENT or
PendingIntent . FLAG _IMMUTABLE // FLAG_IMMUTABLE for Android 12+
PendingIntent . FLAG _IMMUTABLE // FLAG_IMMUTABLE for Android 12+
)
)
try {
try {
pendingIntent . send ( )
pendingIntent . send ( )
} catch ( foregroundServiceStartException : IllegalStateException ) {
} catch ( foregroundServiceStartException : IllegalStateException ) {
@ -462,7 +411,6 @@ open class UninitializedApp : Application() {
TSLog . e ( TAG , " startVPN hit exception: $e " )
TSLog . e ( TAG , " startVPN hit exception: $e " )
}
}
}
}
fun stopVPN ( ) {
fun stopVPN ( ) {
val intent = Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _STOP _VPN }
val intent = Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _STOP _VPN }
try {
try {
@ -473,7 +421,6 @@ open class UninitializedApp : Application() {
TSLog . e ( TAG , " stopVPN hit exception in startService(): $e " )
TSLog . e ( TAG , " stopVPN hit exception in startService(): $e " )
}
}
}
}
fun restartVPN ( ) {
fun restartVPN ( ) {
val intent =
val intent =
Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _RESTART _VPN }
Intent ( this , IPNService :: class . java ) . apply { action = IPNService . ACTION _RESTART _VPN }
@ -485,14 +432,12 @@ open class UninitializedApp : Application() {
TSLog . e ( TAG , " restartVPN hit exception in startService(): $e " )
TSLog . e ( TAG , " restartVPN hit exception in startService(): $e " )
}
}
}
}
fun createNotificationChannel ( id : String , name : String , description : String , importance : Int ) {
fun createNotificationChannel ( id : String , name : String , description : String , importance : Int ) {
val channel = NotificationChannel ( id , name , importance )
val channel = NotificationChannel ( id , name , importance )
channel . description = description
channel . description = description
notificationManager = NotificationManagerCompat . from ( this )
notificationManager = NotificationManagerCompat . from ( this )
notificationManager . createNotificationChannel ( channel )
notificationManager . createNotificationChannel ( channel )
}
}
fun notifyStatus (
fun notifyStatus (
vpnRunning : Boolean ,
vpnRunning : Boolean ,
hideDisconnectAction : Boolean ,
hideDisconnectAction : Boolean ,
@ -500,7 +445,6 @@ open class UninitializedApp : Application() {
) {
) {
notifyStatus ( buildStatusNotification ( vpnRunning , hideDisconnectAction , exitNodeName ) )
notifyStatus ( buildStatusNotification ( vpnRunning , hideDisconnectAction , exitNodeName ) )
}
}
fun notifyStatus ( notification : Notification ) {
fun notifyStatus ( notification : Notification ) {
if ( ActivityCompat . checkSelfPermission ( this , Manifest . permission . POST _NOTIFICATIONS ) !=
if ( ActivityCompat . checkSelfPermission ( this , Manifest . permission . POST _NOTIFICATIONS ) !=
PackageManager . PERMISSION _GRANTED ) {
PackageManager . PERMISSION _GRANTED ) {
@ -515,7 +459,6 @@ open class UninitializedApp : Application() {
}
}
notificationManager . notify ( STATUS _NOTIFICATION _ID , notification )
notificationManager . notify ( STATUS _NOTIFICATION _ID , notification )
}
}
fun buildStatusNotification (
fun buildStatusNotification (
vpnRunning : Boolean ,
vpnRunning : Boolean ,
hideDisconnectAction : Boolean ,
hideDisconnectAction : Boolean ,
@ -537,7 +480,6 @@ open class UninitializedApp : Application() {
0 ,
0 ,
buttonIntent ,
buttonIntent ,
PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
val intent =
val intent =
Intent ( this , MainActivity :: class . java ) . apply {
Intent ( this , MainActivity :: class . java ) . apply {
flags = Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK
flags = Intent . FLAG _ACTIVITY _NEW _TASK or Intent . FLAG _ACTIVITY _CLEAR _TASK
@ -545,7 +487,6 @@ open class UninitializedApp : Application() {
val pendingIntent : PendingIntent =
val pendingIntent : PendingIntent =
PendingIntent . getActivity (
PendingIntent . getActivity (
this , 1 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
this , 1 , intent , PendingIntent . FLAG _UPDATE _CURRENT or PendingIntent . FLAG _IMMUTABLE )
val builder =
val builder =
NotificationCompat . Builder ( this , STATUS _CHANNEL _ID )
NotificationCompat . Builder ( this , STATUS _CHANNEL _ID )
. setSmallIcon ( icon )
. setSmallIcon ( icon )
@ -563,18 +504,14 @@ open class UninitializedApp : Application() {
}
}
return builder . build ( )
return builder . build ( )
}
}
fun updateUserDisallowedPackageNames ( packageNames : List < String > ) {
fun updateUserDisallowedPackageNames ( packageNames : List < String > ) {
if ( packageNames . any { it . isEmpty ( ) } ) {
if ( packageNames . any { it . isEmpty ( ) } ) {
TSLog . e ( TAG , " updateUserDisallowedPackageNames called with empty packageName(s) " )
TSLog . e ( TAG , " updateUserDisallowedPackageNames called with empty packageName(s) " )
return
return
}
}
getUnencryptedPrefs ( ) . edit ( ) . putStringSet ( DISALLOWED _APPS _KEY , packageNames . toSet ( ) ) . apply ( )
getUnencryptedPrefs ( ) . edit ( ) . putStringSet ( DISALLOWED _APPS _KEY , packageNames . toSet ( ) ) . apply ( )
this . restartVPN ( )
this . restartVPN ( )
}
}
fun disallowedPackageNames ( ) : List < String > {
fun disallowedPackageNames ( ) : List < String > {
val mdmDisallowed =
val mdmDisallowed =
MDMSettings . excludedPackages . flow . value . value ?. split ( " , " ) ?. map { it . trim ( ) } ?: emptyList ( )
MDMSettings . excludedPackages . flow . value . value ?. split ( " , " ) ?. map { it . trim ( ) } ?: emptyList ( )
@ -587,8 +524,8 @@ open class UninitializedApp : Application() {
return builtInDisallowedPackageNames + userDisallowed
return builtInDisallowedPackageNames + userDisallowed
}
}
fun getAppScopedViewModel ( ) : Vpn ViewModel {
fun getAppScopedViewModel ( ) : App ViewModel {
return vpn ViewModel
return app ViewModel
}
}
val builtInDisallowedPackageNames : List < String > =
val builtInDisallowedPackageNames : List < String > =
@ -616,4 +553,4 @@ open class UninitializedApp : Application() {
// Android Connectivity Service https://github.com/tailscale/tailscale/issues/14128
// Android Connectivity Service https://github.com/tailscale/tailscale/issues/14128
" com.google.android.apps.scone " ,
" com.google.android.apps.scone " ,
)
)
}
}