diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-09-18 14:03:48 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-09-18 20:29:23 +0200 |
commit | d200437813ae09769dc90820ded3911a324601ca (patch) | |
tree | a0092ae87fb90afa70c67c4ed3715c5f1c2e1da6 | |
parent | 3ffe7a5e68dcd6e1f3e589d904b723aa4eb24a38 (diff) |
ui: move to Jetpack DataStore instead of SharedPrefs
Hopefully PreferencesPreferenceDataStore gets to go away sometime down
the line.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
10 files changed, 304 insertions, 101 deletions
diff --git a/build.gradle b/build.gradle index 086a6f95..50ff4873 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ buildscript { coordinatorLayoutVersion = '1.1.0' coreKtxVersion = '1.3.1' coroutinesVersion = '1.3.9' + datastoreVersion = '1.0.0-alpha01' desugarVersion = '1.0.10' fragmentVersion = '1.3.0-alpha07' jsr305Version = '3.0.2' diff --git a/ui/build.gradle b/ui/build.gradle index b764143d..4c16fb15 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -68,6 +68,7 @@ dependencies { implementation "androidx.fragment:fragment-ktx:$fragmentVersion" implementation "androidx.preference:preference-ktx:$preferenceVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion" + implementation "androidx.datastore:datastore-preferences:$datastoreVersion" implementation "com.github.material-components:material-components-android:$materialComponentsVersion" implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" diff --git a/ui/src/main/java/com/wireguard/android/Application.kt b/ui/src/main/java/com/wireguard/android/Application.kt index 537d5416..fe98d0d2 100644 --- a/ui/src/main/java/com/wireguard/android/Application.kt +++ b/ui/src/main/java/com/wireguard/android/Application.kt @@ -6,15 +6,15 @@ package com.wireguard.android import android.content.Context import android.content.Intent -import android.content.SharedPreferences -import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Build import android.os.StrictMode import android.os.StrictMode.ThreadPolicy import android.os.StrictMode.VmPolicy import android.util.Log import androidx.appcompat.app.AppCompatDelegate -import androidx.preference.PreferenceManager +import androidx.datastore.DataStore +import androidx.datastore.preferences.Preferences +import androidx.datastore.preferences.createDataStore import com.wireguard.android.backend.Backend import com.wireguard.android.backend.GoBackend import com.wireguard.android.backend.WgQuickBackend @@ -23,22 +23,27 @@ import com.wireguard.android.model.TunnelManager import com.wireguard.android.util.ModuleLoader import com.wireguard.android.util.RootShell import com.wireguard.android.util.ToolsInstaller +import com.wireguard.android.util.UserKnobs +import com.wireguard.android.util.applicationScope import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.util.Locale -class Application : android.app.Application(), OnSharedPreferenceChangeListener { +class Application : android.app.Application() { private val futureBackend = CompletableDeferred<Backend>() private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate) private var backend: Backend? = null private lateinit var moduleLoader: ModuleLoader private lateinit var rootShell: RootShell - private lateinit var sharedPreferences: SharedPreferences + private lateinit var preferencesDataStore: DataStore<Preferences> private lateinit var toolsInstaller: ToolsInstaller private lateinit var tunnelManager: TunnelManager @@ -58,7 +63,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener } } - private fun determineBackend(): Backend { + private suspend fun determineBackend(): Backend { var backend: Backend? = null var didStartRootShell = false if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) { @@ -69,19 +74,22 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener } catch (ignored: Exception) { } } - if (!sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) { + if (!UserKnobs.disableKernelModule.first() && ModuleLoader.isModuleLoaded()) { try { if (!didStartRootShell) rootShell.start() val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller) - wgQuickBackend.setMultipleTunnels(sharedPreferences.getBoolean("multiple_tunnels", false)) + wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first()) backend = wgQuickBackend + UserKnobs.multipleTunnels.onEach { + wgQuickBackend.setMultipleTunnels(it) + }.launchIn(coroutineScope) } catch (ignored: Exception) { } } if (backend == null) { backend = GoBackend(applicationContext) - GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true) } + GoBackend.setAlwaysOnCallback { get().applicationScope.launch { get().tunnelManager.restoreState(true) } } } return backend } @@ -92,10 +100,12 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener rootShell = RootShell(applicationContext) toolsInstaller = ToolsInstaller(applicationContext, rootShell) moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT) - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext) + preferencesDataStore = applicationContext.createDataStore(name = "settings") if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - AppCompatDelegate.setDefaultNightMode( - if (sharedPreferences.getBoolean("dark_theme", false)) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO) + coroutineScope.launch { + AppCompatDelegate.setDefaultNightMode( + if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO) + } } else { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) } @@ -109,16 +119,9 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener Log.e(TAG, Log.getStackTraceString(e)) } } - sharedPreferences.registerOnSharedPreferenceChangeListener(this) - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - if ("multiple_tunnels" == key && backend != null && backend is WgQuickBackend) - (backend as WgQuickBackend).setMultipleTunnels(sharedPreferences.getBoolean(key, false)) } override fun onTerminate() { - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) coroutineScope.cancel() super.onTerminate() } @@ -143,7 +146,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener fun getRootShell() = get().rootShell @JvmStatic - fun getSharedPreferences() = get().sharedPreferences + fun getPreferencesDataStore() = get().preferencesDataStore @JvmStatic fun getToolsInstaller() = get().toolsInstaller diff --git a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt index feac43ca..214947d4 100644 --- a/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt +++ b/ui/src/main/java/com/wireguard/android/activity/SettingsActivity.kt @@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat import com.wireguard.android.Application import com.wireguard.android.R import com.wireguard.android.backend.WgQuickBackend +import com.wireguard.android.preference.PreferencesPreferenceDataStore import com.wireguard.android.util.AdminKnobs import com.wireguard.android.util.ModuleLoader import kotlinx.coroutines.Dispatchers @@ -43,6 +44,7 @@ class SettingsActivity : ThemeChangeAwareActivity() { class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) { + preferenceManager.preferenceDataStore = PreferencesPreferenceDataStore(lifecycleScope, Application.getPreferencesDataStore()) addPreferencesFromResource(R.xml.preferences) preferenceScreen.initialExpandedChildrenCount = 4 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { diff --git a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt index bd124cbc..3ebfbaaa 100644 --- a/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt +++ b/ui/src/main/java/com/wireguard/android/activity/ThemeChangeAwareActivity.kt @@ -4,39 +4,30 @@ */ package com.wireguard.android.activity -import android.content.SharedPreferences -import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import com.wireguard.android.Application +import androidx.lifecycle.lifecycleScope +import com.wireguard.android.util.UserKnobs +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach -abstract class ThemeChangeAwareActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { +abstract class ThemeChangeAwareActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this) - } - } - - override fun onDestroy() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - Application.getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this) - } - super.onDestroy() - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { - when (key) { - "dark_theme" -> { - AppCompatDelegate.setDefaultNightMode(if (sharedPreferences.getBoolean(key, false)) { + UserKnobs.darkTheme.onEach { + val newMode = if (it) { AppCompatDelegate.MODE_NIGHT_YES } else { AppCompatDelegate.MODE_NIGHT_NO - }) - recreate() - } + } + if (AppCompatDelegate.getDefaultNightMode() != newMode) { + AppCompatDelegate.setDefaultNightMode(newMode) + recreate() + } + }.launchIn(lifecycleScope) } } } diff --git a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt index f4a0f1b4..fa22b524 100644 --- a/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt +++ b/ui/src/main/java/com/wireguard/android/model/TunnelManager.kt @@ -4,7 +4,6 @@ */ package com.wireguard.android.model -import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -14,7 +13,6 @@ import androidx.databinding.BaseObservable import androidx.databinding.Bindable import com.wireguard.android.Application.Companion.get import com.wireguard.android.Application.Companion.getBackend -import com.wireguard.android.Application.Companion.getSharedPreferences import com.wireguard.android.Application.Companion.getTunnelManager import com.wireguard.android.BR import com.wireguard.android.R @@ -22,6 +20,7 @@ import com.wireguard.android.backend.Statistics import com.wireguard.android.backend.Tunnel import com.wireguard.android.configStore.ConfigStore import com.wireguard.android.databinding.ObservableSortedKeyedArrayList +import com.wireguard.android.util.UserKnobs import com.wireguard.android.util.applicationScope import com.wireguard.config.Config import kotlinx.coroutines.CompletableDeferred @@ -29,6 +28,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -84,16 +84,12 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { } @get:Bindable - @SuppressLint("ApplySharedPref") var lastUsedTunnel: ObservableTunnel? = null private set(value) { if (value == field) return field = value notifyPropertyChanged(BR.lastUsedTunnel) - if (value != null) - getSharedPreferences().edit().putString(KEY_LAST_USED_TUNNEL, value.name).commit() - else - getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).commit() + applicationScope.launch { UserKnobs.setLastUsedTunnel(value?.name) } } suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) { @@ -113,12 +109,14 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) { for (name in present) addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN) - val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null) - if (lastUsedName != null) - lastUsedTunnel = tunnelMap[lastUsedName] - haveLoaded = true - restoreState(true) - tunnels.complete(tunnelMap) + applicationScope.launch { + val lastUsedName = UserKnobs.lastUsedTunnel.first() + if (lastUsedName != null) + lastUsedTunnel = tunnelMap[lastUsedName] + haveLoaded = true + restoreState(true) + tunnels.complete(tunnelMap) + } } private fun refreshTunnelStates() { @@ -133,26 +131,22 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { } } - fun restoreState(force: Boolean) { - if (!haveLoaded || (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false))) + suspend fun restoreState(force: Boolean) { + if (!haveLoaded || (!force && !UserKnobs.restoreOnBoot.first())) return - val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null) - ?: return + val previouslyRunning = UserKnobs.runningTunnels.first() if (previouslyRunning.isEmpty()) return - applicationScope.launch { - withContext(Dispatchers.IO) { - try { - tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll() - } catch (e: Throwable) { - Log.e(TAG, Log.getStackTraceString(e)) - } + withContext(Dispatchers.IO) { + try { + tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll() + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) } } } - @SuppressLint("ApplySharedPref") - fun saveState() { - getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit() + suspend fun saveState() { + UserKnobs.setRunningTunnels(tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()) } suspend fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): Config = withContext(Dispatchers.Main.immediate) { @@ -216,23 +210,23 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { class IntentReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { - val manager = getTunnelManager() - if (intent == null) return - val action = intent.action ?: return - if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) { - manager.refreshTunnelStates() - return - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !getSharedPreferences().getBoolean("allow_remote_control_intents", false)) - return - val state: Tunnel.State - state = when (action) { - "com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP - "com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN - else -> return - } - val tunnelName = intent.getStringExtra("tunnel") ?: return applicationScope.launch { + val manager = getTunnelManager() + if (intent == null) return@launch + val action = intent.action ?: return@launch + if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) { + manager.refreshTunnelStates() + return@launch + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !UserKnobs.allowRemoteControlIntents.first()) + return@launch + val state: Tunnel.State + state = when (action) { + "com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP + "com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN + else -> return@launch + } + val tunnelName = intent.getStringExtra("tunnel") ?: return@launch val tunnels = manager.getTunnels() val tunnel = tunnels[tunnelName] ?: return@launch manager.setTunnelState(tunnel, state) @@ -250,9 +244,5 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() { companion object { private const val TAG = "WireGuard/TunnelManager" - - private const val KEY_LAST_USED_TUNNEL = "last_used_tunnel" - private const val KEY_RESTORE_ON_BOOT = "restore_on_boot" - private const val KEY_RUNNING_TUNNELS = "enabled_configs" } } diff --git a/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt b/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt index 3d47d2ea..5d21a541 100644 --- a/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt +++ b/ui/src/main/java/com/wireguard/android/preference/KernelModuleDisablerPreference.kt @@ -4,7 +4,6 @@ */ package com.wireguard.android.preference -import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.util.AttributeSet @@ -15,6 +14,7 @@ import com.wireguard.android.R import com.wireguard.android.activity.SettingsActivity import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.WgQuickBackend +import com.wireguard.android.util.UserKnobs import com.wireguard.android.util.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -38,16 +38,15 @@ class KernelModuleDisablerPreference(context: Context, attrs: AttributeSet?) : P override fun getTitle() = if (state == State.UNKNOWN) "" else context.getString(state.titleResourceId) - @SuppressLint("ApplySharedPref") override fun onClick() { - if (state == State.DISABLED) { - setState(State.ENABLING) - Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit() - } else if (state == State.ENABLED) { - setState(State.DISABLING) - Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit() - } lifecycleScope.launch { + if (state == State.DISABLED) { + setState(State.ENABLING) + UserKnobs.setDisableKernelModule(false) + } else if (state == State.ENABLED) { + setState(State.DISABLING) + UserKnobs.setDisableKernelModule(true) + } val observableTunnels = Application.getTunnelManager().getTunnels() val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } } try { diff --git a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt index 7e437982..5ba2c4f0 100644 --- a/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt +++ b/ui/src/main/java/com/wireguard/android/preference/ModuleDownloaderPreference.kt @@ -4,7 +4,6 @@ */ package com.wireguard.android.preference -import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.system.OsConstants @@ -15,6 +14,7 @@ import com.wireguard.android.Application import com.wireguard.android.R import com.wireguard.android.activity.SettingsActivity import com.wireguard.android.util.ErrorMessages +import com.wireguard.android.util.UserKnobs import com.wireguard.android.util.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -27,7 +27,6 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe override fun getTitle() = context.getString(R.string.module_installer_title) - @SuppressLint("ApplySharedPref") override fun onClick() { setState(State.WORKING) lifecycleScope.launch { @@ -36,7 +35,7 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe OsConstants.ENOENT -> setState(State.NOTFOUND) OsConstants.EXIT_SUCCESS -> { setState(State.SUCCESS) - Application.getSharedPreferences().edit().remove("disable_kernel_module").commit() + UserKnobs.setDisableKernelModule(null) withContext(Dispatchers.IO) { val restartIntent = Intent(context, SettingsActivity::class.java) restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt b/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt new file mode 100644 index 00000000..96ca2b1c --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/preference/PreferencesPreferenceDataStore.kt @@ -0,0 +1,132 @@ +/* + * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.preference + +import androidx.datastore.DataStore +import androidx.datastore.preferences.Preferences +import androidx.datastore.preferences.edit +import androidx.datastore.preferences.preferencesKey +import androidx.datastore.preferences.preferencesSetKey +import androidx.datastore.preferences.remove +import androidx.preference.PreferenceDataStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +class PreferencesPreferenceDataStore(private val coroutineScope: CoroutineScope, private val dataStore: DataStore<Preferences>) : PreferenceDataStore() { + override fun putString(key: String?, value: String?) { + if (key == null) return + val pk = preferencesKey<String>(key) + coroutineScope.launch { + dataStore.edit { + if (value == null) it.remove(pk) + else it[pk] = value + } + } + } + + override fun putStringSet(key: String?, values: Set<String?>?) { + if (key == null) return + val pk = preferencesSetKey<String>(key) + val filteredValues = values?.filterNotNull()?.toSet() + coroutineScope.launch { + dataStore.edit { + if (filteredValues == null || filteredValues.isEmpty()) it.remove(pk) + else it[pk] = filteredValues + } + } + } + + override fun putInt(key: String?, value: Int) { + if (key == null) return + val pk = preferencesKey<Int>(key) + coroutineScope.launch { + dataStore.edit { + it[pk] = value + } + } + } + + override fun putLong(key: String?, value: Long) { + if (key == null) return + val pk = preferencesKey<Long>(key) + coroutineScope.launch { + dataStore.edit { + it[pk] = value + } + } + } + + override fun putFloat(key: String?, value: Float) { + if (key == null) return + val pk = preferencesKey<Float>(key) + coroutineScope.launch { + dataStore.edit { + it[pk] = value + } + } + } + + override fun putBoolean(key: String?, value: Boolean) { + if (key == null) return + val pk = preferencesKey<Boolean>(key) + coroutineScope.launch { + dataStore.edit { + it[pk] = value + } + } + } + + override fun getString(key: String?, defValue: String?): String? { + if (key == null) return defValue + val pk = preferencesKey<String>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValue }.first() + } + } + + override fun getStringSet(key: String?, defValues: Set<String?>?): Set<String?>? { + if (key == null) return defValues + val pk = preferencesSetKey<String>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValues }.first() + } + } + + override fun getInt(key: String?, defValue: Int): Int { + if (key == null) return defValue + val pk = preferencesKey<Int>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValue }.first() + } + } + + override fun getLong(key: String?, defValue: Long): Long { + if (key == null) return defValue + val pk = preferencesKey<Long>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValue }.first() + } + } + + override fun getFloat(key: String?, defValue: Float): Float { + if (key == null) return defValue + val pk = preferencesKey<Float>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValue }.first() + } + } + + override fun getBoolean(key: String?, defValue: Boolean): Boolean { + if (key == null) return defValue + val pk = preferencesKey<Boolean>(key) + return runBlocking { + dataStore.data.map { it[pk] ?: defValue }.first() + } + } +} diff --git a/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt b/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt new file mode 100644 index 00000000..b0107ebf --- /dev/null +++ b/ui/src/main/java/com/wireguard/android/util/UserKnobs.kt @@ -0,0 +1,85 @@ +/* + * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.android.util + +import androidx.datastore.preferences.edit +import androidx.datastore.preferences.preferencesKey +import androidx.datastore.preferences.preferencesSetKey +import androidx.datastore.preferences.remove +import com.wireguard.android.Application +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +object UserKnobs { + private val DISABLE_KERNEL_MODULE = preferencesKey<Boolean>("disable_kernel_module") + val disableKernelModule: Flow<Boolean> + get() = Application.getPreferencesDataStore().data.map { + it[DISABLE_KERNEL_MODULE] ?: false + } + + suspend fun setDisableKernelModule(disable: Boolean?) { + Application.getPreferencesDataStore().edit { + if (disable == null) + it.remove(DISABLE_KERNEL_MODULE) + else + it[DISABLE_KERNEL_MODULE] = disable + } + } + + private val MULTIPLE_TUNNELS = preferencesKey<Boolean>("multiple_tunnels") + val multipleTunnels: Flow<Boolean> + get() = Application.getPreferencesDataStore().data.map { + it[MULTIPLE_TUNNELS] ?: false + } + + private val DARK_THEME = preferencesKey<Boolean>("dark_theme") + val darkTheme: Flow<Boolean> + get() = Application.getPreferencesDataStore().data.map { + it[DARK_THEME] ?: false + } + + private val ALLOW_REMOTE_CONTROL_INTENTS = preferencesKey<Boolean>("allow_remote_control_intents") + val allowRemoteControlIntents: Flow<Boolean> + get() = Application.getPreferencesDataStore().data.map { + it[ALLOW_REMOTE_CONTROL_INTENTS] ?: false + } + + private val RESTORE_ON_BOOT = preferencesKey<Boolean>("restore_on_boot") + val restoreOnBoot: Flow<Boolean> + get() = Application.getPreferencesDataStore().data.map { + it[RESTORE_ON_BOOT] ?: false + } + + private val LAST_USED_TUNNEL = preferencesKey<String>("last_used_tunnel") + val lastUsedTunnel: Flow<String?> + get() = Application.getPreferencesDataStore().data.map { + it[LAST_USED_TUNNEL] + } + + suspend fun setLastUsedTunnel(lastUsedTunnel: String?) { + Application.getPreferencesDataStore().edit { + if (lastUsedTunnel == null) + it.remove(LAST_USED_TUNNEL) + else + it[LAST_USED_TUNNEL] = lastUsedTunnel + } + } + + private val RUNNING_TUNNELS = preferencesSetKey<String>("enabled_configs") + val runningTunnels: Flow<Set<String>> + get() = Application.getPreferencesDataStore().data.map { + it[RUNNING_TUNNELS] ?: emptySet() + } + + suspend fun setRunningTunnels(runningTunnels: Set<String>) { + Application.getPreferencesDataStore().edit { + if (runningTunnels.isEmpty()) + it.remove(RUNNING_TUNNELS) + else + it[RUNNING_TUNNELS] = runningTunnels + } + } +} |