diff --git a/android/src/main/java/com/tailscale/ipn/App.java b/android/src/main/java/com/tailscale/ipn/App.java index b37c6e3..c70d8cd 100644 --- a/android/src/main/java/com/tailscale/ipn/App.java +++ b/android/src/main/java/com/tailscale/ipn/App.java @@ -164,7 +164,7 @@ public class App extends Application { return getEncryptedPrefs().getString(prefKey, null); } - private SharedPreferences getEncryptedPrefs() throws IOException, GeneralSecurityException { + public SharedPreferences getEncryptedPrefs() throws IOException, GeneralSecurityException { MasterKey key = new MasterKey.Builder(this) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); diff --git a/android/src/main/java/com/tailscale/ipn/MainActivity.kt b/android/src/main/java/com/tailscale/ipn/MainActivity.kt index acc39b1..6804693 100644 --- a/android/src/main/java/com/tailscale/ipn/MainActivity.kt +++ b/android/src/main/java/com/tailscale/ipn/MainActivity.kt @@ -1,12 +1,12 @@ // Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause - package com.tailscale.ipn - import android.content.Intent import android.net.Uri +import android.content.Context +import android.content.RestrictionsManager import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -16,6 +16,7 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.tailscale.ipn.mdm.MDMSettings import com.tailscale.ipn.ui.service.IpnManager import com.tailscale.ipn.ui.theme.AppTheme import com.tailscale.ipn.ui.view.AboutView @@ -102,5 +103,12 @@ class MainActivity : ComponentActivity() { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(browserIntent) } + + + override fun onResume() { + super.onResume() + val restrictionsManager = this.getSystemService(Context.RESTRICTIONS_SERVICE) as RestrictionsManager + manager.mdmSettings = MDMSettings(restrictionsManager) + } } diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt index 81c82c2..517db14 100644 --- a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettings.kt @@ -3,12 +3,35 @@ package com.tailscale.ipn.mdm -enum class NetworkDevices(val value: String) { - currentUser("current-user"), - otherUsers("other-users"), - taggedDevices("tagged-devices"), -} +import android.content.RestrictionsManager +import com.tailscale.ipn.App + +class MDMSettings(private val restrictionsManager: RestrictionsManager? = null) { + // TODO(angott): implement a typed enum string array type + val hiddenNetworkDevices: List = emptyList() + + fun get(setting: BooleanSetting): Boolean { + restrictionsManager?.let { restrictionsManager -> + restrictionsManager.applicationRestrictions.containsKey(setting.key) + return restrictionsManager.applicationRestrictions.getBoolean(setting.key) + } ?: run { + return App.getApplication().encryptedPrefs.getBoolean(setting.key, false) + } + } + + fun get(setting: StringSetting): String? { + return App.getApplication().encryptedPrefs.getString(setting.key, null) + } + + fun get(setting: AlwaysNeverUserDecidesSetting): AlwaysNeverUserDecidesValue { + val storedString = App.getApplication().encryptedPrefs.getString(setting.key, "user-decides") + ?: "user-decides" + return AlwaysNeverUserDecidesValue.valueOf(storedString) + } -class MDMSettings { - val hiddenNetworkDevices: List = emptyList() + fun get(setting: ShowHideSetting): ShowHideValue { + val storedString = App.getApplication().encryptedPrefs.getString(setting.key, "show") + ?: "show" + return ShowHideValue.valueOf(storedString) + } } diff --git a/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsDefinitions.kt b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsDefinitions.kt new file mode 100644 index 0000000..9d25940 --- /dev/null +++ b/android/src/main/java/com/tailscale/ipn/mdm/MDMSettingsDefinitions.kt @@ -0,0 +1,55 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +package com.tailscale.ipn.mdm + +enum class BooleanSetting(val key: String, val localizedTitle: String) { + ForceEnabled("ForceEnabled", "Force Enabled Connection Toggle") +} + +enum class StringSetting(val key: String, val localizedTitle: String) { + ExitNodeID("ExitNodeID", "Forced Exit Node: Stable ID"), + KeyExpirationNotice("KeyExpirationNotice", "Key Expiration Notice Period"), + LoginURL("LoginURL", "Custom control server URL"), + ManagedByCaption("ManagedByCaption", "Managed By - Caption"), + ManagedByOrganizationName("ManagedByOrganizationName", "Managed By - Organization Name"), + ManagedByURL("ManagedByURL", "Managed By - Support URL"), + Tailnet("Tailnet", "Recommended/Required Tailnet Name"), +} + +// A setting representing a String value which is set to either `always`, `never` or `user-decides`. +enum class AlwaysNeverUserDecidesSetting(val key: String, val localizedTitle: String) { + AllowIncomingConnections("AllowIncomingConnections", "Allow Incoming Connections"), + DetectThirdPartyAppConflicts("DetectThirdPartyAppConflicts", "Detect potentially problematic third-party apps"), + ExitNodeAllowLANAccess("ExitNodeAllowLANAccess", "Allow LAN Access when using an exit node"), + PostureChecking("PostureChecking", "Enable Posture Checking"), + UseTailscaleDNSSettings("UseTailscaleDNSSettings", "Use Tailscale DNS Settings"), + UseTailscaleSubnets("UseTailscaleSubnets", "Use Tailscale Subnets") +} + +enum class AlwaysNeverUserDecidesValue(val value: String) { + Always("always"), + Never("never"), + UserDecides("user-decides") +} + +// A setting representing a String value which is set to either `show` or `hide`. +enum class ShowHideSetting(val key: String, val localizedTitle: String) { + ExitNodesPicker("ExitNodesPicker", "Exit Nodes Picker"), + ManageTailnetLock("ManageTailnetLock", "“Manage Tailnet lock” menu item"), + ResetToDefaults("ResetToDefaults", "“Reset to Defaults” menu item"), + RunExitNode("RunExitNode", "Run as Exit Node"), + TestMenu("TestMenu", "Show Debug Menu"), + UpdateMenu("UpdateMenu", "“Update Available” menu item"), +} + +enum class ShowHideValue(val value: String) { + Show("show"), + Hide("hide") +} + +enum class NetworkDevices(val value: String) { + currentUser("current-user"), + otherUsers("other-users"), + taggedDevices("tagged-devices"), +} \ No newline at end of file diff --git a/android/src/main/java/com/tailscale/ipn/ui/service/IpnManager.kt b/android/src/main/java/com/tailscale/ipn/ui/service/IpnManager.kt index 99da3a0..fc79618 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/service/IpnManager.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/service/IpnManager.kt @@ -7,6 +7,7 @@ package com.tailscale.ipn.ui.service import android.content.Intent import com.tailscale.ipn.App import com.tailscale.ipn.IPNReceiver +import com.tailscale.ipn.mdm.MDMSettings import com.tailscale.ipn.ui.localapi.LocalApiClient import com.tailscale.ipn.ui.model.Ipn import com.tailscale.ipn.ui.notifier.Notifier @@ -33,6 +34,7 @@ class IpnManager { private var scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) var apiClient = LocalApiClient(scope) + var mdmSettings = MDMSettings() val model = IpnModel(notifier, apiClient, scope) val actions = IpnActions(