You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale-android/android/src/main/java/com/tailscale/ipn/App.kt

437 lines
16 KiB
Kotlin

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn
import android.Manifest
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.tailscale.ipn.mdm.MDMSettings
import com.tailscale.ipn.ui.localapi.Client
import com.tailscale.ipn.ui.localapi.Request
import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.notifier.Notifier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import libtailscale.Libtailscale
import java.io.File
import java.io.IOException
import java.net.InetAddress
import java.net.NetworkInterface
import java.security.GeneralSecurityException
import java.util.Locale
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
class App : UninitializedApp(), libtailscale.AppContext {
val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
companion object {
private const val FILE_CHANNEL_ID = "tailscale-files"
private const val TAG = "App"
private val networkConnectivityRequest =
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build()
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
private lateinit var appInstance: App
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
/**
* 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.
*/
@JvmStatic
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
fun get(): App {
appInstance.initOnce()
return appInstance
}
}
val dns = DnsConfig()
private lateinit var connectivityManager: ConnectivityManager
private lateinit var app: libtailscale.Application
override fun getPlatformDNSConfig(): String = dns.dnsConfigAsString
override fun isPlayVersion(): Boolean = MaybeGoogle.isGoogle()
override fun log(s: String, s1: String) {
Log.d(s, s1)
}
override fun onCreate() {
super.onCreate()
createNotificationChannel(
STATUS_CHANNEL_ID,
getString(R.string.vpn_status),
getString(R.string.optional_notifications_which_display_the_status_of_the_vpn_tunnel),
NotificationManagerCompat.IMPORTANCE_MIN)
createNotificationChannel(
FILE_CHANNEL_ID,
getString(R.string.taildrop_file_transfers),
getString(R.string.notifications_delivered_when_a_file_is_received_using_taildrop),
NotificationManagerCompat.IMPORTANCE_DEFAULT)
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
appInstance = this
setUnprotectedInstance(this)
}
override fun onTerminate() {
super.onTerminate()
Notifier.stop()
applicationScope.cancel()
}
private var isInitialized = false
@Synchronized
private fun initOnce() {
if (isInitialized) {
return
}
isInitialized = true
val dataDir = this.filesDir.absolutePath
// Set this to enable direct mode for taildrop whereby downloads will be saved directly
// to the given folder. We will preferentially use <shared>/Downloads and fallback to
// an app local directory "Taildrop" if we cannot create that. This mode does not support
// user notifications for incoming files.
val directFileDir = this.prepareDownloadsFolder()
app = Libtailscale.start(dataDir, directFileDir.absolutePath, this)
Request.setApp(app)
Notifier.setApp(app)
Notifier.start(applicationScope)
connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
setAndRegisterNetworkCallbacks()
applicationScope.launch {
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
Notifier.state.collect { state ->
val ableToStartVPN = state > Ipn.State.NeedsMachineAuth
val vpnRunning = state == Ipn.State.Starting || state == Ipn.State.Running
updateConnStatus(ableToStartVPN, vpnRunning)
QuickToggleService.setVPNRunning(vpnRunning)
}
}
}
fun setWantRunning(wantRunning: Boolean) {
val callback: (Result<Ipn.Prefs>) -> Unit = { result ->
result.fold(
onSuccess = {},
onFailure = { error ->
Log.d("TAG", "Set want running: failed to update preferences: ${error.message}")
})
}
Client(applicationScope)
.editPrefs(Ipn.MaskedPrefs().apply { WantRunning = wantRunning }, callback)
}
// requestNetwork attempts to find the best network that matches the passed NetworkRequest. It is
// possible that this might return an unusuable network, eg a captive portal.
private fun setAndRegisterNetworkCallbacks() {
connectivityManager.requestNetwork(
networkConnectivityRequest,
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val sb = StringBuilder()
val linkProperties: LinkProperties? = connectivityManager.getLinkProperties(network)
val dnsList: MutableList<InetAddress> = linkProperties?.dnsServers ?: mutableListOf()
for (ip in dnsList) {
sb.append(ip.hostAddress).append(" ")
}
val searchDomains: String? = linkProperties?.domains
if (searchDomains != null) {
sb.append("\n")
sb.append(searchDomains)
}
if (dns.updateDNSFromNetwork(sb.toString())) {
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
Libtailscale.onDNSConfigChanged(linkProperties?.interfaceName)
}
}
override fun onLost(network: Network) {
super.onLost(network)
if (dns.updateDNSFromNetwork("")) {
Libtailscale.onDNSConfigChanged("")
}
}
})
}
// encryptToPref a byte array of data using the Jetpack Security
// library and writes it to a global encrypted preference store.
@Throws(IOException::class, GeneralSecurityException::class)
override fun encryptToPref(prefKey: String?, plaintext: String?) {
getEncryptedPrefs().edit().putString(prefKey, plaintext).commit()
}
// decryptFromPref decrypts a encrypted preference using the Jetpack Security
// library and returns the plaintext.
@Throws(IOException::class, GeneralSecurityException::class)
override fun decryptFromPref(prefKey: String?): String? {
return getEncryptedPrefs().getString(prefKey, null)
}
@Throws(IOException::class, GeneralSecurityException::class)
fun getEncryptedPrefs(): SharedPreferences {
val key = MasterKey.Builder(this).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
return EncryptedSharedPreferences.create(
this,
"secret_shared_prefs",
key,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM)
}
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
/*
* 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
* value without needing a fully initialized instance of the application.
*/
private fun updateConnStatus(ableToStartVPN: Boolean, vpnRunning: Boolean) {
setAbleToStartVPN(ableToStartVPN)
QuickToggleService.updateTile()
Log.d("App", "Set Tile Ready: $ableToStartVPN")
notifyStatus(vpnRunning)
}
override fun getModelName(): String {
val manu = Build.MANUFACTURER
var model = Build.MODEL
// Strip manufacturer from model.
val idx = model.lowercase(Locale.getDefault()).indexOf(manu.lowercase(Locale.getDefault()))
if (idx != -1) {
model = model.substring(idx + manu.length).trim()
}
return "$manu $model"
}
override fun getOSVersion(): String = Build.VERSION.RELEASE
override fun isChromeOS(): Boolean {
return packageManager.hasSystemFeature("android.hardware.type.pc")
}
override fun getInterfacesAsString(): String {
val interfaces: ArrayList<NetworkInterface> =
java.util.Collections.list(NetworkInterface.getNetworkInterfaces())
val sb = StringBuilder()
for (nif in interfaces) {
try {
sb.append(
String.format(
Locale.ROOT,
"%s %d %d %b %b %b %b %b |",
nif.name,
nif.index,
nif.mtu,
nif.isUp,
nif.supportsMulticast(),
nif.isLoopback,
nif.isPointToPoint,
nif.supportsMulticast()))
for (ia in nif.interfaceAddresses) {
val parts = ia.toString().split("/", limit = 0)
if (parts.size > 1) {
sb.append(String.format(Locale.ROOT, "%s/%d ", parts[1], ia.networkPrefixLength))
}
}
} catch (e: Exception) {
continue
}
sb.append("\n")
}
return sb.toString()
}
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
private fun prepareDownloadsFolder(): File {
var downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
try {
if (!downloads.exists()) {
downloads.mkdirs()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to create downloads folder: $e")
downloads = File(this.filesDir, "Taildrop")
try {
if (!downloads.exists()) {
downloads.mkdirs()
}
} catch (e: Exception) {
Log.e(TAG, "Failed to create Taildrop folder: $e")
downloads = File("")
}
}
return downloads
}
@Throws(
IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class)
override fun getSyspolicyBooleanValue(key: String): Boolean {
return getSyspolicyStringValue(key) == "true"
}
@Throws(
IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class)
override fun getSyspolicyStringValue(key: String): String {
return MDMSettings.allSettingsByKey[key]?.flow?.value?.toString()
?: run {
Log.d("MDM", "$key is not defined on Android. Throwing NoSuchKeyException.")
throw MDMSettings.NoSuchKeyException()
}
}
@Throws(
IOException::class, GeneralSecurityException::class, MDMSettings.NoSuchKeyException::class)
override fun getSyspolicyStringArrayJSONValue(key: String): String {
val list = MDMSettings.allSettingsByKey[key]?.flow?.value as? List<String>
try {
return Json.encodeToString(list)
} catch (e: Exception) {
Log.d("MDM", "$key is not defined on Android. Throwing NoSuchKeyException.")
throw MDMSettings.NoSuchKeyException()
}
}
}
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
/**
* 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
* effects from starting the Go backend (such as launching the VPN).
*/
open class UninitializedApp : Application() {
companion object {
const val STATUS_NOTIFICATION_ID = 1
const val STATUS_CHANNEL_ID = "tailscale-status"
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
// 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"
// File for shared preferences that are not encrypted.
private const val UNENCRYPTED_PREFERENCES = "unencrypted"
private lateinit var appInstance: UninitializedApp
@JvmStatic
fun get(): UninitializedApp {
return appInstance
}
}
protected fun setUnprotectedInstance(instance: UninitializedApp) {
appInstance = instance
}
protected fun setAbleToStartVPN(rdy: Boolean) {
getUnencryptedPrefs().edit().putBoolean(ABLE_TO_START_VPN_KEY, rdy).apply()
}
/** This function can be called without initializing the App. */
fun isAbleToStartVPN(): Boolean {
return getUnencryptedPrefs().getBoolean(ABLE_TO_START_VPN_KEY, false)
}
private fun getUnencryptedPrefs(): SharedPreferences {
return getSharedPreferences(UNENCRYPTED_PREFERENCES, MODE_PRIVATE)
}
fun startVPN() {
val intent = Intent(this, IPNService::class.java).apply { action = IPNService.ACTION_START_VPN }
startForegroundService(intent)
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
}
fun stopVPN() {
val intent = Intent(this, IPNService::class.java).apply { action = IPNService.ACTION_STOP_VPN }
startService(intent)
}
fun createNotificationChannel(id: String, name: String, description: String, importance: Int) {
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
val channel = NotificationChannel(id, name, importance)
channel.description = description
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
val nm: NotificationManagerCompat = NotificationManagerCompat.from(this)
nm.createNotificationChannel(channel)
}
protected fun notifyStatus(vpnRunning: Boolean) {
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
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 buttonIntent = Intent(this, IPNReceiver::class.java).apply { this.action = action }
val pendingButtonIntent: PendingIntent =
PendingIntent.getBroadcast(
this,
0,
buttonIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val intent =
Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
PendingIntent.getActivity(
this, 1, 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, pendingButtonIntent).build())
.setContentIntent(pendingIntent)
.build()
android: fix quick settings tile status (#377) * android: fix quick settings tile https://github.com/tailscale/tailscale-android/pull/358 updated the Quick Settings tile to only depend on ipn state. This was only partially correct in the sense that we made changes to only check for whether the state was > stopped and not whether Tailscale was on. This checks for two states, whether Tailscale is on, and whether the tile is ready to be used. The former requires ipn state to be >= Starting, and the latter checks whether ipn state is > RequiresMachineAuth. Tile readiness determines whether an intent is to open MainActivity or whether an intent to connect/disconnect VPN is sent. Whether Tailscale is on or off determines whether the tile status is active or not. We lazily initialize App to avoid starting Tailscale when unnecessary - for example, when viewing the QuickSettings tile, there's no need to start Tailscale's backend. We also persistently store a flag indicating whether VPN can be started by quick settings tile: this allows us to start the VPN from the quick settings tile even when the application was previously stopped. Updates tailscale/tailscale#11920 Co-authored-by: kari-ts <kari@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com> * android: simplify IPNService lifecycle Reserves use of IPNReceiver only for external requests to start the VPN. Updates tailscale/corp#19860 Signed-off-by: Percy Wegmann <percy@tailscale.com> * Revert "android: temporarily remove quick settings tile" This reverts commit edb3f5b0c52ae5cb9cc2fa89310e395df5c08c6e. Signed-off-by: Percy Wegmann <percy@tailscale.com> --------- Signed-off-by: Percy Wegmann <percy@tailscale.com> Co-authored-by: Percy Wegmann <percy@tailscale.com>
2 months ago
}
}