Implement direct boot awareness
To do direct boot, important files need to be moved to device storage (i.e. not as safe storage). The only thing sensitive is user credentials (server settings in each profile) and the config file passed to libss-*.so. In order to implement direct boot, at least the active profile has to be moved/copied to device storage. This commit keeps profile information (and temporary data used by ProfileConfigFragment) and Shadowsocks config in credential storage and move everything else to device storage. In order to make direct boot work, active profile is also copied into device storage and Shadowsocks config file is created in device storage during direct boot. Cleanup of those information is also implemented. Fixes #1457. NB: * `android:directBootAware` can't be used on application element. That seems to be used by system apps. * Works fine with multi-user; * Old GSM files are not migrated (see App.kt) to device storage.
This commit is contained in:
@@ -21,115 +21,72 @@
|
||||
package com.github.shadowsocks.preference
|
||||
|
||||
import android.os.Binder
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
import com.github.shadowsocks.database.DBHelper
|
||||
import com.github.shadowsocks.database.KeyValuePair
|
||||
import com.github.shadowsocks.BootReceiver
|
||||
import com.github.shadowsocks.database.PrivateDatabase
|
||||
import com.github.shadowsocks.database.PublicDatabase
|
||||
import com.github.shadowsocks.utils.DirectBoot
|
||||
import com.github.shadowsocks.utils.Key
|
||||
import com.github.shadowsocks.utils.parsePort
|
||||
import java.util.*
|
||||
|
||||
@Suppress("MemberVisibilityCanPrivate", "unused")
|
||||
object DataStore : PreferenceDataStore() {
|
||||
fun getBoolean(key: String?) = DBHelper.kvPairDao.queryForId(key)?.boolean
|
||||
fun getFloat(key: String?) = DBHelper.kvPairDao.queryForId(key)?.float
|
||||
fun getInt(key: String?) = DBHelper.kvPairDao.queryForId(key)?.int
|
||||
fun getLong(key: String?) = DBHelper.kvPairDao.queryForId(key)?.long
|
||||
fun getString(key: String?) = DBHelper.kvPairDao.queryForId(key)?.string
|
||||
fun getStringSet(key: String?) = DBHelper.kvPairDao.queryForId(key)?.stringSet
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
|
||||
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
|
||||
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue
|
||||
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue
|
||||
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue
|
||||
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(key) ?: defValue
|
||||
|
||||
fun putBoolean(key: String?, value: Boolean?) = if (value == null) remove(key) else putBoolean(key, value)
|
||||
fun putFloat(key: String?, value: Float?) = if (value == null) remove(key) else putFloat(key, value)
|
||||
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
|
||||
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
|
||||
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
fun remove(key: String?) {
|
||||
DBHelper.kvPairDao.deleteById(key)
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
|
||||
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
|
||||
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
|
||||
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
|
||||
object DataStore {
|
||||
val publicStore = OrmLitePreferenceDataStore(PublicDatabase.kvPairDao)
|
||||
// privateStore will only be used as temp storage for ProfileConfigFragment
|
||||
val privateStore = OrmLitePreferenceDataStore(PrivateDatabase.kvPairDao)
|
||||
|
||||
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
|
||||
private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() }
|
||||
private fun getLocalPort(key: String, default: Int): Int {
|
||||
val value = getInt(key)
|
||||
val value = publicStore.getInt(key)
|
||||
return if (value != null) {
|
||||
putString(key, value.toString())
|
||||
publicStore.putString(key, value.toString())
|
||||
value
|
||||
} else parsePort(getString(key), default + userIndex)
|
||||
} else parsePort(publicStore.getString(key), default + userIndex)
|
||||
}
|
||||
|
||||
var profileId: Int
|
||||
get() = getInt(Key.id) ?: 0
|
||||
set(value) = putInt(Key.id, value)
|
||||
get() = publicStore.getInt(Key.id) ?: 0
|
||||
set(value) {
|
||||
publicStore.putInt(Key.id, value)
|
||||
if (DataStore.directBootAware) DirectBoot.update()
|
||||
}
|
||||
/**
|
||||
* Setter is defined in MainActivity.onPreferenceDataStoreChanged.
|
||||
*/
|
||||
val directBootAware: Boolean get() = BootReceiver.enabled && (publicStore.getBoolean(Key.directBootAware) ?: false)
|
||||
var serviceMode: String
|
||||
get() = getString(Key.serviceMode) ?: Key.modeVpn
|
||||
set(value) = putString(Key.serviceMode, value)
|
||||
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
|
||||
set(value) = publicStore.putString(Key.serviceMode, value)
|
||||
var portProxy: Int
|
||||
get() = getLocalPort(Key.portProxy, 1080)
|
||||
set(value) = putString(Key.portProxy, value.toString())
|
||||
set(value) = publicStore.putString(Key.portProxy, value.toString())
|
||||
var portLocalDns: Int
|
||||
get() = getLocalPort(Key.portLocalDns, 5450)
|
||||
set(value) = putString(Key.portLocalDns, value.toString())
|
||||
set(value) = publicStore.putString(Key.portLocalDns, value.toString())
|
||||
var portTransproxy: Int
|
||||
get() = getLocalPort(Key.portTransproxy, 8200)
|
||||
set(value) = putString(Key.portTransproxy, value.toString())
|
||||
|
||||
var proxyApps: Boolean
|
||||
get() = getBoolean(Key.proxyApps) ?: false
|
||||
set(value) = putBoolean(Key.proxyApps, value)
|
||||
var bypass: Boolean
|
||||
get() = getBoolean(Key.bypass) ?: false
|
||||
set(value) = putBoolean(Key.bypass, value)
|
||||
var individual: String
|
||||
get() = getString(Key.individual) ?: ""
|
||||
set(value) = putString(Key.individual, value)
|
||||
var plugin: String
|
||||
get() = getString(Key.plugin) ?: ""
|
||||
set(value) = putString(Key.plugin, value)
|
||||
var dirty: Boolean
|
||||
get() = getBoolean(Key.dirty) ?: false
|
||||
set(value) = putBoolean(Key.dirty, value)
|
||||
set(value) = publicStore.putString(Key.portTransproxy, value.toString())
|
||||
|
||||
fun initGlobal() {
|
||||
// temporary workaround for support lib bug
|
||||
if (getString(Key.serviceMode) == null) serviceMode = serviceMode
|
||||
if (getString(Key.portProxy) == null) portProxy = portProxy
|
||||
if (getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
|
||||
if (getString(Key.portTransproxy) == null) portTransproxy = portTransproxy
|
||||
if (publicStore.getString(Key.serviceMode) == null) serviceMode = serviceMode
|
||||
if (publicStore.getString(Key.portProxy) == null) portProxy = portProxy
|
||||
if (publicStore.getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
|
||||
if (publicStore.getString(Key.portTransproxy) == null) portTransproxy = portTransproxy
|
||||
}
|
||||
|
||||
var proxyApps: Boolean
|
||||
get() = privateStore.getBoolean(Key.proxyApps) ?: false
|
||||
set(value) = privateStore.putBoolean(Key.proxyApps, value)
|
||||
var bypass: Boolean
|
||||
get() = privateStore.getBoolean(Key.bypass) ?: false
|
||||
set(value) = privateStore.putBoolean(Key.bypass, value)
|
||||
var individual: String
|
||||
get() = privateStore.getString(Key.individual) ?: ""
|
||||
set(value) = privateStore.putString(Key.individual, value)
|
||||
var plugin: String
|
||||
get() = privateStore.getString(Key.plugin) ?: ""
|
||||
set(value) = privateStore.putString(Key.plugin, value)
|
||||
var dirty: Boolean
|
||||
get() = privateStore.getBoolean(Key.dirty) ?: false
|
||||
set(value) = privateStore.putBoolean(Key.dirty, value)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
|
||||
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
*******************************************************************************/
|
||||
|
||||
package com.github.shadowsocks.preference
|
||||
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
import com.github.shadowsocks.database.KeyValuePair
|
||||
import com.j256.ormlite.dao.Dao
|
||||
import java.util.HashSet
|
||||
|
||||
@Suppress("MemberVisibilityCanPrivate", "unused")
|
||||
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() {
|
||||
fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean
|
||||
fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float
|
||||
fun getInt(key: String?) = kvPairDao.queryForId(key)?.int
|
||||
fun getLong(key: String?) = kvPairDao.queryForId(key)?.long
|
||||
fun getString(key: String?) = kvPairDao.queryForId(key)?.string
|
||||
fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
|
||||
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
|
||||
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue
|
||||
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue
|
||||
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue
|
||||
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(key) ?: defValue
|
||||
|
||||
fun putBoolean(key: String?, value: Boolean?) = if (value == null) remove(key) else putBoolean(key, value)
|
||||
fun putFloat(key: String?, value: Float?) = if (value == null) remove(key) else putFloat(key, value)
|
||||
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
|
||||
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putFloat(key: String?, value: Float) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putInt(key: String?, value: Int) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putLong(key: String?, value: Long) {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
|
||||
kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
fun remove(key: String?) {
|
||||
kvPairDao.deleteById(key)
|
||||
fireChangeListener(key)
|
||||
}
|
||||
|
||||
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
|
||||
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
|
||||
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
|
||||
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
|
||||
}
|
||||
Reference in New Issue
Block a user