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>
This commit is contained in:
parent
3ffe7a5e68
commit
d200437813
@ -11,6 +11,7 @@ buildscript {
|
|||||||
coordinatorLayoutVersion = '1.1.0'
|
coordinatorLayoutVersion = '1.1.0'
|
||||||
coreKtxVersion = '1.3.1'
|
coreKtxVersion = '1.3.1'
|
||||||
coroutinesVersion = '1.3.9'
|
coroutinesVersion = '1.3.9'
|
||||||
|
datastoreVersion = '1.0.0-alpha01'
|
||||||
desugarVersion = '1.0.10'
|
desugarVersion = '1.0.10'
|
||||||
fragmentVersion = '1.3.0-alpha07'
|
fragmentVersion = '1.3.0-alpha07'
|
||||||
jsr305Version = '3.0.2'
|
jsr305Version = '3.0.2'
|
||||||
|
@ -68,6 +68,7 @@ dependencies {
|
|||||||
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
|
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
|
||||||
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleRuntimeKtxVersion"
|
||||||
|
implementation "androidx.datastore:datastore-preferences:$datastoreVersion"
|
||||||
implementation "com.github.material-components:material-components-android:$materialComponentsVersion"
|
implementation "com.github.material-components:material-components-android:$materialComponentsVersion"
|
||||||
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
implementation "com.journeyapps:zxing-android-embedded:$zxingEmbeddedVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
||||||
|
@ -6,15 +6,15 @@ package com.wireguard.android
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import android.os.StrictMode.ThreadPolicy
|
import android.os.StrictMode.ThreadPolicy
|
||||||
import android.os.StrictMode.VmPolicy
|
import android.os.StrictMode.VmPolicy
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
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.Backend
|
||||||
import com.wireguard.android.backend.GoBackend
|
import com.wireguard.android.backend.GoBackend
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
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.ModuleLoader
|
||||||
import com.wireguard.android.util.RootShell
|
import com.wireguard.android.util.RootShell
|
||||||
import com.wireguard.android.util.ToolsInstaller
|
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.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class Application : android.app.Application(), OnSharedPreferenceChangeListener {
|
class Application : android.app.Application() {
|
||||||
private val futureBackend = CompletableDeferred<Backend>()
|
private val futureBackend = CompletableDeferred<Backend>()
|
||||||
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate)
|
private val coroutineScope = CoroutineScope(Job() + Dispatchers.Main.immediate)
|
||||||
private var backend: Backend? = null
|
private var backend: Backend? = null
|
||||||
private lateinit var moduleLoader: ModuleLoader
|
private lateinit var moduleLoader: ModuleLoader
|
||||||
private lateinit var rootShell: RootShell
|
private lateinit var rootShell: RootShell
|
||||||
private lateinit var sharedPreferences: SharedPreferences
|
private lateinit var preferencesDataStore: DataStore<Preferences>
|
||||||
private lateinit var toolsInstaller: ToolsInstaller
|
private lateinit var toolsInstaller: ToolsInstaller
|
||||||
private lateinit var tunnelManager: TunnelManager
|
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 backend: Backend? = null
|
||||||
var didStartRootShell = false
|
var didStartRootShell = false
|
||||||
if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) {
|
if (!ModuleLoader.isModuleLoaded() && moduleLoader.moduleMightExist()) {
|
||||||
@ -69,19 +74,22 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
|
|||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sharedPreferences.getBoolean("disable_kernel_module", false) && ModuleLoader.isModuleLoaded()) {
|
if (!UserKnobs.disableKernelModule.first() && ModuleLoader.isModuleLoaded()) {
|
||||||
try {
|
try {
|
||||||
if (!didStartRootShell)
|
if (!didStartRootShell)
|
||||||
rootShell.start()
|
rootShell.start()
|
||||||
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
|
val wgQuickBackend = WgQuickBackend(applicationContext, rootShell, toolsInstaller)
|
||||||
wgQuickBackend.setMultipleTunnels(sharedPreferences.getBoolean("multiple_tunnels", false))
|
wgQuickBackend.setMultipleTunnels(UserKnobs.multipleTunnels.first())
|
||||||
backend = wgQuickBackend
|
backend = wgQuickBackend
|
||||||
|
UserKnobs.multipleTunnels.onEach {
|
||||||
|
wgQuickBackend.setMultipleTunnels(it)
|
||||||
|
}.launchIn(coroutineScope)
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (backend == null) {
|
if (backend == null) {
|
||||||
backend = GoBackend(applicationContext)
|
backend = GoBackend(applicationContext)
|
||||||
GoBackend.setAlwaysOnCallback { get().tunnelManager.restoreState(true) }
|
GoBackend.setAlwaysOnCallback { get().applicationScope.launch { get().tunnelManager.restoreState(true) } }
|
||||||
}
|
}
|
||||||
return backend
|
return backend
|
||||||
}
|
}
|
||||||
@ -92,10 +100,12 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
|
|||||||
rootShell = RootShell(applicationContext)
|
rootShell = RootShell(applicationContext)
|
||||||
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
toolsInstaller = ToolsInstaller(applicationContext, rootShell)
|
||||||
moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
|
moduleLoader = ModuleLoader(applicationContext, rootShell, USER_AGENT)
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
preferencesDataStore = applicationContext.createDataStore(name = "settings")
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
coroutineScope.launch {
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
if (sharedPreferences.getBoolean("dark_theme", false)) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
if (UserKnobs.darkTheme.first()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||||
}
|
}
|
||||||
@ -109,16 +119,9 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
|
|||||||
Log.e(TAG, Log.getStackTraceString(e))
|
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() {
|
override fun onTerminate() {
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
|
||||||
coroutineScope.cancel()
|
coroutineScope.cancel()
|
||||||
super.onTerminate()
|
super.onTerminate()
|
||||||
}
|
}
|
||||||
@ -143,7 +146,7 @@ class Application : android.app.Application(), OnSharedPreferenceChangeListener
|
|||||||
fun getRootShell() = get().rootShell
|
fun getRootShell() = get().rootShell
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getSharedPreferences() = get().sharedPreferences
|
fun getPreferencesDataStore() = get().preferencesDataStore
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getToolsInstaller() = get().toolsInstaller
|
fun getToolsInstaller() = get().toolsInstaller
|
||||||
|
@ -14,6 +14,7 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
import com.wireguard.android.Application
|
import com.wireguard.android.Application
|
||||||
import com.wireguard.android.R
|
import com.wireguard.android.R
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
|
import com.wireguard.android.preference.PreferencesPreferenceDataStore
|
||||||
import com.wireguard.android.util.AdminKnobs
|
import com.wireguard.android.util.AdminKnobs
|
||||||
import com.wireguard.android.util.ModuleLoader
|
import com.wireguard.android.util.ModuleLoader
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -43,6 +44,7 @@ class SettingsActivity : ThemeChangeAwareActivity() {
|
|||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, key: String?) {
|
||||||
|
preferenceManager.preferenceDataStore = PreferencesPreferenceDataStore(lifecycleScope, Application.getPreferencesDataStore())
|
||||||
addPreferencesFromResource(R.xml.preferences)
|
addPreferencesFromResource(R.xml.preferences)
|
||||||
preferenceScreen.initialExpandedChildrenCount = 4
|
preferenceScreen.initialExpandedChildrenCount = 4
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
@ -4,39 +4,30 @@
|
|||||||
*/
|
*/
|
||||||
package com.wireguard.android.activity
|
package com.wireguard.android.activity
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
Application.getSharedPreferences().registerOnSharedPreferenceChangeListener(this)
|
UserKnobs.darkTheme.onEach {
|
||||||
}
|
val newMode = if (it) {
|
||||||
}
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
AppCompatDelegate.MODE_NIGHT_YES
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
} else {
|
} else {
|
||||||
AppCompatDelegate.MODE_NIGHT_NO
|
AppCompatDelegate.MODE_NIGHT_NO
|
||||||
})
|
}
|
||||||
|
if (AppCompatDelegate.getDefaultNightMode() != newMode) {
|
||||||
|
AppCompatDelegate.setDefaultNightMode(newMode)
|
||||||
recreate()
|
recreate()
|
||||||
}
|
}
|
||||||
|
}.launchIn(lifecycleScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.wireguard.android.model
|
package com.wireguard.android.model
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -14,7 +13,6 @@ import androidx.databinding.BaseObservable
|
|||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import com.wireguard.android.Application.Companion.get
|
import com.wireguard.android.Application.Companion.get
|
||||||
import com.wireguard.android.Application.Companion.getBackend
|
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.Application.Companion.getTunnelManager
|
||||||
import com.wireguard.android.BR
|
import com.wireguard.android.BR
|
||||||
import com.wireguard.android.R
|
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.backend.Tunnel
|
||||||
import com.wireguard.android.configStore.ConfigStore
|
import com.wireguard.android.configStore.ConfigStore
|
||||||
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
import com.wireguard.android.databinding.ObservableSortedKeyedArrayList
|
||||||
|
import com.wireguard.android.util.UserKnobs
|
||||||
import com.wireguard.android.util.applicationScope
|
import com.wireguard.android.util.applicationScope
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
@ -29,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@ -84,16 +84,12 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
var lastUsedTunnel: ObservableTunnel? = null
|
var lastUsedTunnel: ObservableTunnel? = null
|
||||||
private set(value) {
|
private set(value) {
|
||||||
if (value == field) return
|
if (value == field) return
|
||||||
field = value
|
field = value
|
||||||
notifyPropertyChanged(BR.lastUsedTunnel)
|
notifyPropertyChanged(BR.lastUsedTunnel)
|
||||||
if (value != null)
|
applicationScope.launch { UserKnobs.setLastUsedTunnel(value?.name) }
|
||||||
getSharedPreferences().edit().putString(KEY_LAST_USED_TUNNEL, value.name).commit()
|
|
||||||
else
|
|
||||||
getSharedPreferences().edit().remove(KEY_LAST_USED_TUNNEL).commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
|
suspend fun getTunnelConfig(tunnel: ObservableTunnel): Config = withContext(Dispatchers.Main.immediate) {
|
||||||
@ -113,13 +109,15 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
|||||||
private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) {
|
private fun onTunnelsLoaded(present: Iterable<String>, running: Collection<String>) {
|
||||||
for (name in present)
|
for (name in present)
|
||||||
addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN)
|
addToList(name, null, if (running.contains(name)) Tunnel.State.UP else Tunnel.State.DOWN)
|
||||||
val lastUsedName = getSharedPreferences().getString(KEY_LAST_USED_TUNNEL, null)
|
applicationScope.launch {
|
||||||
|
val lastUsedName = UserKnobs.lastUsedTunnel.first()
|
||||||
if (lastUsedName != null)
|
if (lastUsedName != null)
|
||||||
lastUsedTunnel = tunnelMap[lastUsedName]
|
lastUsedTunnel = tunnelMap[lastUsedName]
|
||||||
haveLoaded = true
|
haveLoaded = true
|
||||||
restoreState(true)
|
restoreState(true)
|
||||||
tunnels.complete(tunnelMap)
|
tunnels.complete(tunnelMap)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshTunnelStates() {
|
private fun refreshTunnelStates() {
|
||||||
applicationScope.launch {
|
applicationScope.launch {
|
||||||
@ -133,26 +131,22 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreState(force: Boolean) {
|
suspend fun restoreState(force: Boolean) {
|
||||||
if (!haveLoaded || (!force && !getSharedPreferences().getBoolean(KEY_RESTORE_ON_BOOT, false)))
|
if (!haveLoaded || (!force && !UserKnobs.restoreOnBoot.first()))
|
||||||
return
|
return
|
||||||
val previouslyRunning = getSharedPreferences().getStringSet(KEY_RUNNING_TUNNELS, null)
|
val previouslyRunning = UserKnobs.runningTunnels.first()
|
||||||
?: return
|
|
||||||
if (previouslyRunning.isEmpty()) return
|
if (previouslyRunning.isEmpty()) return
|
||||||
applicationScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
|
tunnelMap.filter { previouslyRunning.contains(it.name) }.map { async(Dispatchers.IO + SupervisorJob()) { setTunnelState(it, Tunnel.State.UP) } }.awaitAll()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.e(TAG, Log.getStackTraceString(e))
|
Log.e(TAG, Log.getStackTraceString(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
suspend fun saveState() {
|
||||||
fun saveState() {
|
UserKnobs.setRunningTunnels(tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet())
|
||||||
getSharedPreferences().edit().putStringSet(KEY_RUNNING_TUNNELS, tunnelMap.filter { it.state == Tunnel.State.UP }.map { it.name }.toSet()).commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setTunnelConfig(tunnel: ObservableTunnel, config: Config): Config = withContext(Dispatchers.Main.immediate) {
|
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() {
|
class IntentReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
applicationScope.launch {
|
||||||
val manager = getTunnelManager()
|
val manager = getTunnelManager()
|
||||||
if (intent == null) return
|
if (intent == null) return@launch
|
||||||
val action = intent.action ?: return
|
val action = intent.action ?: return@launch
|
||||||
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
|
if ("com.wireguard.android.action.REFRESH_TUNNEL_STATES" == action) {
|
||||||
manager.refreshTunnelStates()
|
manager.refreshTunnelStates()
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !getSharedPreferences().getBoolean("allow_remote_control_intents", false))
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !UserKnobs.allowRemoteControlIntents.first())
|
||||||
return
|
return@launch
|
||||||
val state: Tunnel.State
|
val state: Tunnel.State
|
||||||
state = when (action) {
|
state = when (action) {
|
||||||
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
|
"com.wireguard.android.action.SET_TUNNEL_UP" -> Tunnel.State.UP
|
||||||
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
|
"com.wireguard.android.action.SET_TUNNEL_DOWN" -> Tunnel.State.DOWN
|
||||||
else -> return
|
else -> return@launch
|
||||||
}
|
}
|
||||||
val tunnelName = intent.getStringExtra("tunnel") ?: return
|
val tunnelName = intent.getStringExtra("tunnel") ?: return@launch
|
||||||
applicationScope.launch {
|
|
||||||
val tunnels = manager.getTunnels()
|
val tunnels = manager.getTunnels()
|
||||||
val tunnel = tunnels[tunnelName] ?: return@launch
|
val tunnel = tunnels[tunnelName] ?: return@launch
|
||||||
manager.setTunnelState(tunnel, state)
|
manager.setTunnelState(tunnel, state)
|
||||||
@ -250,9 +244,5 @@ class TunnelManager(private val configStore: ConfigStore) : BaseObservable() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "WireGuard/TunnelManager"
|
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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.wireguard.android.preference
|
package com.wireguard.android.preference
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
@ -15,6 +14,7 @@ import com.wireguard.android.R
|
|||||||
import com.wireguard.android.activity.SettingsActivity
|
import com.wireguard.android.activity.SettingsActivity
|
||||||
import com.wireguard.android.backend.Tunnel
|
import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
|
import com.wireguard.android.util.UserKnobs
|
||||||
import com.wireguard.android.util.lifecycleScope
|
import com.wireguard.android.util.lifecycleScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
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)
|
override fun getTitle() = if (state == State.UNKNOWN) "" else context.getString(state.titleResourceId)
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
|
lifecycleScope.launch {
|
||||||
if (state == State.DISABLED) {
|
if (state == State.DISABLED) {
|
||||||
setState(State.ENABLING)
|
setState(State.ENABLING)
|
||||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", false).commit()
|
UserKnobs.setDisableKernelModule(false)
|
||||||
} else if (state == State.ENABLED) {
|
} else if (state == State.ENABLED) {
|
||||||
setState(State.DISABLING)
|
setState(State.DISABLING)
|
||||||
Application.getSharedPreferences().edit().putBoolean("disable_kernel_module", true).commit()
|
UserKnobs.setDisableKernelModule(true)
|
||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
|
||||||
val observableTunnels = Application.getTunnelManager().getTunnels()
|
val observableTunnels = Application.getTunnelManager().getTunnels()
|
||||||
val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } }
|
val downings = observableTunnels.map { async(SupervisorJob()) { it.setStateAsync(Tunnel.State.DOWN) } }
|
||||||
try {
|
try {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.wireguard.android.preference
|
package com.wireguard.android.preference
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
@ -15,6 +14,7 @@ import com.wireguard.android.Application
|
|||||||
import com.wireguard.android.R
|
import com.wireguard.android.R
|
||||||
import com.wireguard.android.activity.SettingsActivity
|
import com.wireguard.android.activity.SettingsActivity
|
||||||
import com.wireguard.android.util.ErrorMessages
|
import com.wireguard.android.util.ErrorMessages
|
||||||
|
import com.wireguard.android.util.UserKnobs
|
||||||
import com.wireguard.android.util.lifecycleScope
|
import com.wireguard.android.util.lifecycleScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
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)
|
override fun getTitle() = context.getString(R.string.module_installer_title)
|
||||||
|
|
||||||
@SuppressLint("ApplySharedPref")
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
setState(State.WORKING)
|
setState(State.WORKING)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@ -36,7 +35,7 @@ class ModuleDownloaderPreference(context: Context, attrs: AttributeSet?) : Prefe
|
|||||||
OsConstants.ENOENT -> setState(State.NOTFOUND)
|
OsConstants.ENOENT -> setState(State.NOTFOUND)
|
||||||
OsConstants.EXIT_SUCCESS -> {
|
OsConstants.EXIT_SUCCESS -> {
|
||||||
setState(State.SUCCESS)
|
setState(State.SUCCESS)
|
||||||
Application.getSharedPreferences().edit().remove("disable_kernel_module").commit()
|
UserKnobs.setDisableKernelModule(null)
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val restartIntent = Intent(context, SettingsActivity::class.java)
|
val restartIntent = Intent(context, SettingsActivity::class.java)
|
||||||
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
ui/src/main/java/com/wireguard/android/util/UserKnobs.kt
Normal file
85
ui/src/main/java/com/wireguard/android/util/UserKnobs.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user