Commit 27fc2734 authored by Mygod's avatar Mygod

Replace SharedPreferences with SQLite

Other changes included:

* Fix a bug where dirty flag is not set correctly when changing per-app
  proxy settings;
* Old auto connect settings in SharedPreference is now ignored.
parent 02c8be58
...@@ -92,7 +92,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -92,7 +92,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
proxiedApps.add(item.packageName) proxiedApps.add(item.packageName)
check.setChecked(true) check.setChecked(true)
} }
if (!appsLoading.get) app.editor.putString(Key.individual, proxiedApps.mkString("\n")).apply() if (!appsLoading.get) {
app.dataStore.individual = proxiedApps.mkString("\n")
app.dataStore.dirty = true
}
} }
} }
...@@ -118,7 +121,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -118,7 +121,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
private val appsLoading = new AtomicBoolean private val appsLoading = new AtomicBoolean
private var handler: Handler = _ private var handler: Handler = _
private def initProxiedApps(str: String = app.settings.getString(Key.individual, null)) = private def initProxiedApps(str: String = app.dataStore.individual) =
proxiedApps = str.split('\n').to[mutable.HashSet] proxiedApps = str.split('\n').to[mutable.HashSet]
override def onDestroy() { override def onDestroy() {
...@@ -136,7 +139,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -136,7 +139,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
case R.id.action_apply_all => case R.id.action_apply_all =>
app.profileManager.getAllProfiles match { app.profileManager.getAllProfiles match {
case Some(profiles) => case Some(profiles) =>
val proxiedAppString = app.settings.getString(Key.individual, null) val proxiedAppString = app.dataStore.individual
profiles.foreach(p => { profiles.foreach(p => {
p.individual = proxiedAppString p.individual = proxiedAppString
app.profileManager.updateProfile(p) app.profileManager.updateProfile(p)
...@@ -146,10 +149,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -146,10 +149,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
} }
return true return true
case R.id.action_export => case R.id.action_export =>
val bypass = app.settings.getBoolean(Key.bypass, false) clipboard.setPrimaryClip(ClipData.newPlainText(Key.individual,
val proxiedAppString = app.settings.getString(Key.individual, null) app.dataStore.bypass + "\n" + app.dataStore.individual))
val clip = ClipData.newPlainText(Key.individual, bypass + "\n" + proxiedAppString)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
return true return true
case R.id.action_import => case R.id.action_import =>
...@@ -163,7 +164,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -163,7 +164,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
val (enabled, apps) = if (i < 0) (proxiedAppString, "") val (enabled, apps) = if (i < 0) (proxiedAppString, "")
else (proxiedAppString.substring(0, i), proxiedAppString.substring(i + 1)) else (proxiedAppString.substring(0, i), proxiedAppString.substring(i + 1))
bypassSwitch.setChecked(enabled.toBoolean) bypassSwitch.setChecked(enabled.toBoolean)
app.editor.putString(Key.individual, apps).putBoolean(Key.dirty, true).apply() app.dataStore.individual = apps
app.dataStore.dirty = true
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
appListView.setVisibility(View.GONE) appListView.setVisibility(View.GONE)
fastScroller.setVisibility(View.GONE) fastScroller.setVisibility(View.GONE)
...@@ -202,18 +204,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -202,18 +204,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar.inflateMenu(R.menu.app_manager_menu) toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
if (!app.settings.getBoolean(Key.proxyApps, false)) if (!app.dataStore.proxyApps) {
app.editor.putBoolean(Key.proxyApps, true).putBoolean(Key.dirty, true).apply() app.dataStore.proxyApps = true
app.dataStore.dirty = true
}
findViewById(R.id.onSwitch).asInstanceOf[Switch] findViewById(R.id.onSwitch).asInstanceOf[Switch]
.setOnCheckedChangeListener((_, checked) => { .setOnCheckedChangeListener((_, checked) => {
app.editor.putBoolean(Key.proxyApps, checked).putBoolean(Key.dirty, true).apply() app.dataStore.proxyApps = checked
app.dataStore.dirty = true
finish() finish()
}) })
bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch] bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
bypassSwitch.setChecked(app.settings.getBoolean(Key.bypass, false)) bypassSwitch.setChecked(app.dataStore.bypass)
bypassSwitch.setOnCheckedChangeListener((_, checked) => bypassSwitch.setOnCheckedChangeListener((_, checked) => {
app.editor.putBoolean(Key.bypass, checked).putBoolean(Key.dirty, true).apply()) app.dataStore.bypass = checked
app.dataStore.dirty = true
})
initProxiedApps() initProxiedApps()
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
......
...@@ -20,27 +20,24 @@ ...@@ -20,27 +20,24 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.PreferenceDataStore
import be.mygod.preference.PreferenceFragment import be.mygod.preference.PreferenceFragment
import com.github.shadowsocks.utils.{Key, TcpFastOpen}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.{Key, TcpFastOpen}
class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceChangeListener { class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStoreChangeListener {
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
switch.setOnPreferenceChangeListener((_, value) => { switch.setOnPreferenceChangeListener((_, value) => {
BootReceiver.setEnabled(getActivity, value.asInstanceOf[Boolean]) BootReceiver.setEnabled(getActivity, value.asInstanceOf[Boolean])
true true
}) })
if (getPreferenceManager.getSharedPreferences.getBoolean(Key.isAutoConnect, false)) {
BootReceiver.setEnabled(getActivity, true)
getPreferenceManager.getSharedPreferences.edit.remove(Key.isAutoConnect).apply()
}
switch.setChecked(BootReceiver.getEnabled(getActivity)) switch.setChecked(BootReceiver.getEnabled(getActivity))
val tfo = findPreference(Key.tfo).asInstanceOf[SwitchPreference] val tfo = findPreference(Key.tfo).asInstanceOf[SwitchPreference]
...@@ -56,16 +53,16 @@ class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceCha ...@@ -56,16 +53,16 @@ class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceCha
tfo.setEnabled(false) tfo.setEnabled(false)
tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version"))) tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version")))
} }
app.settings.registerOnSharedPreferenceChangeListener(this) app.dataStore.registerChangeListener(this)
} }
override def onDestroy() { override def onDestroy() {
app.settings.unregisterOnSharedPreferenceChangeListener(this) app.dataStore.unregisterChangeListener(this)
super.onDestroy() super.onDestroy()
} }
def onSharedPreferenceChanged(pref: SharedPreferences, key: String): Unit = key match { def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(pref.getBoolean(key, false)) case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(store.getBoolean(key, false))
case _ => case _ =>
} }
} }
...@@ -26,7 +26,6 @@ import java.util.Locale ...@@ -26,7 +26,6 @@ import java.util.Locale
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.app.{Activity, ProgressDialog} import android.app.{Activity, ProgressDialog}
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content._ import android.content._
import android.net.{Uri, VpnService} import android.net.{Uri, VpnService}
import android.nfc.{NdefMessage, NfcAdapter} import android.nfc.{NdefMessage, NfcAdapter}
...@@ -36,6 +35,7 @@ import android.support.design.widget.{FloatingActionButton, Snackbar} ...@@ -36,6 +35,7 @@ import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.content.res.AppCompatResources import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
...@@ -45,6 +45,7 @@ import com.github.jorgecastilloprz.FABProgressCircle ...@@ -45,6 +45,7 @@ import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, CustomRulesFragment} import com.github.shadowsocks.acl.{Acl, CustomRulesFragment}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.CloseUtils.autoDisconnect import com.github.shadowsocks.utils.CloseUtils.autoDisconnect
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.mikepenz.crossfader.Crossfader import com.mikepenz.crossfader.Crossfader
...@@ -67,7 +68,7 @@ object MainActivity { ...@@ -67,7 +68,7 @@ object MainActivity {
} }
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
with OnSharedPreferenceChangeListener { with OnPreferenceDataStoreChangeListener {
import MainActivity._ import MainActivity._
// UI // UI
...@@ -165,7 +166,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -165,7 +166,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onServiceDisconnected(): Unit = changeState(State.IDLE) override def onServiceDisconnected(): Unit = changeState(State.IDLE)
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener) if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener)
override def binderDied(): Unit = handler.post(() => { override def binderDied(): Unit = handler.post(() => {
detachService() detachService()
...@@ -174,7 +175,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -174,7 +175,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
}) })
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match { override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
case Activity.RESULT_OK => bgService.use(app.profileId) case Activity.RESULT_OK => bgService.use(app.dataStore.profileId)
case _ => Log.e(TAG, "Failed to start VpnService") case _ => Log.e(TAG, "Failed to start VpnService")
} }
...@@ -299,7 +300,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -299,7 +300,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton] fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle] fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
fab.setOnClickListener(_ => if (state == State.CONNECTED) bgService.use(-1) else Utils.ThrowableFuture { fab.setOnClickListener(_ => if (state == State.CONNECTED) bgService.use(-1) else Utils.ThrowableFuture {
if (app.isNatEnabled) bgService.use(app.profileId) else { if (app.isNatEnabled) bgService.use(app.dataStore.profileId) else {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)) else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null))
...@@ -313,7 +314,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -313,7 +314,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
changeState(State.IDLE) // reset everything to init state changeState(State.IDLE) // reset everything to init state
handler.post(() => attachService(callback)) handler.post(() => attachService(callback))
app.settings.registerOnSharedPreferenceChangeListener(this) app.dataStore.registerChangeListener(this)
val intent = getIntent val intent = getIntent
if (intent != null) handleShareIntent(intent) if (intent != null) handleShareIntent(intent)
...@@ -350,7 +351,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -350,7 +351,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
.show() .show()
} }
def onSharedPreferenceChanged(pref: SharedPreferences, key: String): Unit = key match { def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => handler.post(() => { case Key.isNAT => handler.post(() => {
detachService() detachService()
attachService(callback) attachService(callback)
...@@ -424,7 +425,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -424,7 +425,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onDestroy() { override def onDestroy() {
super.onDestroy() super.onDestroy()
app.settings.unregisterOnSharedPreferenceChangeListener(this) app.dataStore.unregisterChangeListener(this)
detachService() detachService()
new BackupManager(this).dataChanged() new BackupManager(this).dataChanged()
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
......
...@@ -49,7 +49,7 @@ class ProfileConfigActivity extends Activity { ...@@ -49,7 +49,7 @@ class ProfileConfigActivity extends Activity {
toolbar.setOnMenuItemClickListener(child) toolbar.setOnMenuItemClickListener(child)
} }
override def onBackPressed(): Unit = if (app.settings.getBoolean(Key.dirty, false)) new AlertDialog.Builder(this) override def onBackPressed(): Unit = if (app.dataStore.dirty) new AlertDialog.Builder(this)
.setTitle(R.string.unsaved_changes_prompt) .setTitle(R.string.unsaved_changes_prompt)
.setPositiveButton(R.string.yes, ((_, _) => child.saveAndExit()): DialogInterface.OnClickListener) .setPositiveButton(R.string.yes, ((_, _) => child.saveAndExit()): DialogInterface.OnClickListener)
.setNegativeButton(R.string.no, ((_, _) => finish()): DialogInterface.OnClickListener) .setNegativeButton(R.string.no, ((_, _) => finish()): DialogInterface.OnClickListener)
......
...@@ -21,13 +21,12 @@ ...@@ -21,13 +21,12 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Activity import android.app.Activity
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content._ import android.content._
import android.os.{Build, Bundle, UserManager} import android.os.{Build, Bundle, UserManager}
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.preference.Preference import android.support.v7.preference.{Preference, PreferenceDataStore}
import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.text.TextUtils import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
...@@ -35,7 +34,7 @@ import be.mygod.preference.{EditTextPreference, PreferenceFragment} ...@@ -35,7 +34,7 @@ import be.mygod.preference.{EditTextPreference, PreferenceFragment}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin._ import com.github.shadowsocks.plugin._
import com.github.shadowsocks.preference.{IconListPreference, PluginConfigurationDialogFragment} import com.github.shadowsocks.preference.{IconListPreference, OnPreferenceDataStoreChangeListener, PluginConfigurationDialogFragment}
import com.github.shadowsocks.utils.{Action, Key, Utils} import com.github.shadowsocks.utils.{Action, Key, Utils}
object ProfileConfigFragment { object ProfileConfigFragment {
...@@ -43,7 +42,7 @@ object ProfileConfigFragment { ...@@ -43,7 +42,7 @@ object ProfileConfigFragment {
} }
class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener
with OnSharedPreferenceChangeListener { with OnPreferenceDataStoreChangeListener {
import ProfileConfigFragment._ import ProfileConfigFragment._
private var profile: Profile = _ private var profile: Profile = _
...@@ -53,10 +52,11 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -53,10 +52,11 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private var pluginConfiguration: PluginConfiguration = _ private var pluginConfiguration: PluginConfiguration = _
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match { app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match {
case Some(p) => case Some(p) =>
profile = p profile = p
profile.serialize(app.editor).apply() profile.serialize(app.dataStore)
case None => getActivity.finish() case None => getActivity.finish()
} }
addPreferencesFromResource(R.xml.pref_profile) addPreferencesFromResource(R.xml.pref_profile)
...@@ -78,7 +78,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -78,7 +78,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
plugin.setOnPreferenceChangeListener((_, value) => { plugin.setOnPreferenceChangeListener((_, value) => {
val selected = value.asInstanceOf[String] val selected = value.asInstanceOf[String]
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected) pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() app.dataStore.plugin = pluginConfiguration.toString
app.dataStore.dirty = true
pluginConfigure.setEnabled(!TextUtils.isEmpty(selected)) pluginConfigure.setEnabled(!TextUtils.isEmpty(selected))
pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) pluginConfigure.setText(pluginConfiguration.selectedOptions.toString)
if (!PluginManager.fetchPlugins()(selected).trusted) if (!PluginManager.fetchPlugins()(selected).trusted)
...@@ -88,7 +89,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -88,7 +89,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
pluginConfigure.setOnPreferenceChangeListener(onPluginConfigureChanged) pluginConfigure.setOnPreferenceChangeListener(onPluginConfigureChanged)
initPlugins() initPlugins()
app.listenForPackageChanges(initPlugins()) app.listenForPackageChanges(initPlugins())
app.settings.registerOnSharedPreferenceChangeListener(this) app.dataStore.registerChangeListener(this)
} }
def initPlugins() { def initPlugins() {
...@@ -97,7 +98,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -97,7 +98,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
plugin.setEntryValues(plugins.map(_._2.id.asInstanceOf[CharSequence]).toArray) plugin.setEntryValues(plugins.map(_._2.id.asInstanceOf[CharSequence]).toArray)
plugin.setEntryIcons(plugins.map(_._2.icon).toArray) plugin.setEntryIcons(plugins.map(_._2.icon).toArray)
plugin.entryPackageNames = plugins.map(_._2.packageName).toArray plugin.entryPackageNames = plugins.map(_._2.packageName).toArray
pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null)) pluginConfiguration = new PluginConfiguration(app.dataStore.plugin)
plugin.setValue(pluginConfiguration.selected) plugin.setValue(pluginConfiguration.selected)
plugin.init() plugin.init()
plugin.checkSummary() plugin.checkSummary()
...@@ -109,7 +110,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -109,7 +110,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
val selected = pluginConfiguration.selected val selected = pluginConfiguration.selected
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions + pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected) (pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() app.dataStore.plugin = pluginConfiguration.toString
app.dataStore.dirty = true
true true
} catch { } catch {
case exc: IllegalArgumentException => case exc: IllegalArgumentException =>
...@@ -117,17 +119,17 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -117,17 +119,17 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
false false
} }
override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit = def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit =
if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply() if (key != Key.proxyApps && findPreference(key) != null) app.dataStore.dirty = true
override def onDestroy() { override def onDestroy() {
app.settings.unregisterOnSharedPreferenceChangeListener(this) app.dataStore.unregisterChangeListener(this)
super.onDestroy() super.onDestroy()
} }
override def onResume() { override def onResume() {
super.onResume() super.onResume()
isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager isProxyApps.setChecked(app.dataStore.proxyApps) // fetch proxyApps updated by AppManager
} }
private def showPluginEditor() { private def showPluginEditor() {
...@@ -176,7 +178,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -176,7 +178,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
} }
def saveAndExit() { def saveAndExit() {
profile.deserialize(app.settings) profile.deserialize(app.dataStore)
app.profileManager.updateProfile(profile) app.profileManager.updateProfile(profile)
if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.deepRefreshId(profile.id) if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.deepRefreshId(profile.id)
getActivity.finish() getActivity.finish()
......
...@@ -68,7 +68,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -68,7 +68,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
case _ => false case _ => false
} }
private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match { private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => id != app.profileId case State.CONNECTED => id != app.dataStore.profileId
case State.STOPPED => true case State.STOPPED => true
case _ => false case _ => false
} }
...@@ -129,7 +129,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -129,7 +129,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx))) TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx)))
} }
if (item.id == app.profileId) { if (item.id == app.dataStore.profileId) {
itemView.setSelected(true) itemView.setSelected(true)
selectedItem = this selectedItem = this
} else { } else {
...@@ -166,7 +166,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -166,7 +166,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
def onClick(v: View): Unit = if (isEnabled) { def onClick(v: View): Unit = if (isEnabled) {
val activity = getActivity.asInstanceOf[MainActivity] val activity = getActivity.asInstanceOf[MainActivity]
val old = app.profileId val old = app.dataStore.profileId
app.switchProfile(item.id) app.switchProfile(item.id)
profilesAdapter.refreshId(old) profilesAdapter.refreshId(old)
itemView.setSelected(true) itemView.setSelected(true)
...@@ -251,7 +251,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -251,7 +251,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
if (index >= 0) { if (index >= 0) {
profiles.remove(index) profiles.remove(index)
notifyItemRemoved(index) notifyItemRemoved(index)
if (id == app.profileId) app.profileId(0) // switch to null profile if (id == app.dataStore.profileId) app.dataStore.profileId = 0 // switch to null profile
} }
} }
} }
...@@ -278,12 +278,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -278,12 +278,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
toolbar.inflateMenu(R.menu.profile_manager_menu) toolbar.inflateMenu(R.menu.profile_manager_menu)
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
if (app.profileManager.getFirstProfile.isEmpty) app.profileId(app.profileManager.createProfile().id) if (app.profileManager.getFirstProfile.isEmpty) app.dataStore.profileId = app.profileManager.createProfile().id
val profilesList = view.findViewById[RecyclerView](R.id.list) val profilesList = view.findViewById[RecyclerView](R.id.list)
val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false) val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false)
profilesList.setLayoutManager(layoutManager) profilesList.setLayoutManager(layoutManager)
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == app.profileId => i case (profile, i) if profile.id == app.dataStore.profileId => i
}.getOrElse(-1)) }.getOrElse(-1))
val animator = new DefaultItemAnimator() val animator = new DefaultItemAnimator()
animator.setSupportsChangeAnimations(false) // prevent fading-in/out when rebinding animator.setSupportsChangeAnimations(false) // prevent fading-in/out when rebinding
......
...@@ -28,12 +28,12 @@ import android.app.{Application, NotificationChannel, NotificationManager} ...@@ -28,12 +28,12 @@ import android.app.{Application, NotificationChannel, NotificationManager}
import android.content._ import android.content._
import android.content.res.Configuration import android.content.res.Configuration
import android.os.{Build, LocaleList} import android.os.{Build, LocaleList}
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager} import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker} import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker}
...@@ -60,13 +60,13 @@ object ShadowsocksApplication { ...@@ -60,13 +60,13 @@ object ShadowsocksApplication {
class ShadowsocksApplication extends Application { class ShadowsocksApplication extends Application {
import ShadowsocksApplication._ import ShadowsocksApplication._
lazy val remoteConfig = FirebaseRemoteConfig.getInstance() lazy val remoteConfig: FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
lazy val tracker: Tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) lazy val tracker: Tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
lazy val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) private lazy val dbHelper = new DBHelper(this)
lazy val editor: SharedPreferences.Editor = settings.edit lazy val profileManager = new ProfileManager(dbHelper)
lazy val profileManager = new ProfileManager(new DBHelper(this)) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper)
def isNatEnabled: Boolean = settings.getBoolean(Key.isNAT, false) def isNatEnabled: Boolean = dataStore.isNAT
def isVpnEnabled: Boolean = !isNatEnabled def isVpnEnabled: Boolean = !isNatEnabled
// send event // send event
...@@ -79,13 +79,11 @@ class ShadowsocksApplication extends Application { ...@@ -79,13 +79,11 @@ class ShadowsocksApplication extends Application {
.setFatal(false) .setFatal(false)
.build()) .build())
def profileId: Int = settings.getInt(Key.id, 0) def currentProfile: Option[Profile] = profileManager.getProfile(dataStore.profileId)
def profileId(i: Int): Unit = editor.putInt(Key.id, i).apply()
def currentProfile: Option[Profile] = profileManager.getProfile(profileId)
def switchProfile(id: Int): Profile = { def switchProfile(id: Int): Profile = {
val result = profileManager.getProfile(id) getOrElse profileManager.createProfile() val result = profileManager.getProfile(id) getOrElse profileManager.createProfile()
profileId(result.id) dataStore.profileId = result.id
result result
} }
...@@ -154,7 +152,7 @@ class ShadowsocksApplication extends Application { ...@@ -154,7 +152,7 @@ class ShadowsocksApplication extends Application {
JobManager.create(this).addJobCreator(DonaldTrump) JobManager.create(this).addJobCreator(DonaldTrump)
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List(
new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN),
...@@ -192,10 +190,10 @@ class ShadowsocksApplication extends Application { ...@@ -192,10 +190,10 @@ class ShadowsocksApplication extends Application {
autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => autoClose(new FileOutputStream(new File(getFilesDir, file)))(out =>
IOUtils.copy(in, out))) IOUtils.copy(in, out)))
} }
editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply() dataStore.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE)
} }
def updateAssets(): Unit = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets() def updateAssets(): Unit = if (dataStore.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
def listenForPackageChanges(callback: => Unit): BroadcastReceiver = { def listenForPackageChanges(callback: => Unit): BroadcastReceiver = {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
......
...@@ -20,26 +20,11 @@ ...@@ -20,26 +20,11 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.backup.{BackupAgentHelper, FileBackupHelper, SharedPreferencesBackupHelper} import android.app.backup.{BackupAgentHelper, FileBackupHelper}
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.database.DBHelper import com.github.shadowsocks.database.DBHelper
class ShadowsocksBackupAgent extends BackupAgentHelper { class ShadowsocksBackupAgent extends BackupAgentHelper {
override def onCreate(): Unit = addHelper("com.github.shadowsocks.database.profile",
// The names of the SharedPreferences groups that the application maintains. These new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE, Acl.CUSTOM_RULES + ".acl"))
// are the same strings that are passed to getSharedPreferences(String, int).
val PREFS_DISPLAY = "com.github.shadowsocks_preferences"
// An arbitrary string used within the BackupAgentHelper implementation to
// identify the SharedPreferencesBackupHelper's data.
val MY_PREFS_BACKUP_KEY = "com.github.shadowsocks"
val DATABASE = "com.github.shadowsocks.database.profile"
override def onCreate() {
val helper = new SharedPreferencesBackupHelper(this, PREFS_DISPLAY)
addHelper(MY_PREFS_BACKUP_KEY, helper)
addHelper(DATABASE, new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE,
Acl.CUSTOM_RULES + ".acl"))
}
} }
...@@ -46,7 +46,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { ...@@ -46,7 +46,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
def startBackgroundService() { def startBackgroundService() {
if (app.isNatEnabled) { if (app.isNatEnabled) {
bgService.use(app.profileId) bgService.use(app.dataStore.profileId)
finish() finish()
} else { } else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this) val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
...@@ -89,7 +89,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { ...@@ -89,7 +89,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
resultCode match { resultCode match {
case Activity.RESULT_OK => case Activity.RESULT_OK =>
if (bgService != null) { if (bgService != null) {
bgService.use(app.profileId) bgService.use(app.dataStore.profileId)
} }
case _ => case _ =>
Log.e(TAG, "Failed to start VpnService") Log.e(TAG, "Failed to start VpnService")
......
...@@ -45,7 +45,7 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext { ...@@ -45,7 +45,7 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
}, 1000) }, 1000)
} }
def startBackgroundService(): Unit = bgService.useSync(app.profileId) def startBackgroundService(): Unit = bgService.useSync(app.dataStore.profileId)
override def onCreate() { override def onCreate() {
super.onCreate() super.onCreate()
......
...@@ -20,10 +20,14 @@ ...@@ -20,10 +20,14 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.content.Context import java.nio.ByteBuffer
import android.content.{Context, SharedPreferences}
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.preference.PreferenceManager
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource import com.j256.ormlite.support.ConnectionSource
...@@ -46,17 +50,20 @@ object DBHelper { ...@@ -46,17 +50,20 @@ object DBHelper {
} }
class DBHelper(val context: Context) class DBHelper(val context: Context)
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 22) { extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 23) {
import DBHelper._ import DBHelper._
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
lazy val kvPairDao: Dao[KeyValuePair, String] = getDao(classOf[KeyValuePair])
def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) { def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
TableUtils.createTable(connectionSource, classOf[Profile]) TableUtils.createTable(connectionSource, classOf[Profile])
TableUtils.createTable(connectionSource, classOf[KeyValuePair])
} }
def recreate(database: SQLiteDatabase, connectionSource: ConnectionSource) { def recreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
TableUtils.dropTable(connectionSource, classOf[Profile], true) TableUtils.dropTable(connectionSource, classOf[Profile], true)
TableUtils.dropTable(connectionSource, classOf[KeyValuePair], true)
onCreate(database, connectionSource) onCreate(database, connectionSource)
} }
...@@ -132,6 +139,19 @@ class DBHelper(val context: Context) ...@@ -132,6 +139,19 @@ class DBHelper(val context: Context)
profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
} }
if (oldVersion < 23) {
import KeyValuePair._
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.isNAT, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.isNAT, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.currentVersionCode, TYPE_INT,
ByteBuffer.allocate(4).putInt(-1).array()))
}
} catch { } catch {
case ex: Exception => case ex: Exception =>
app.track(ex) app.track(ex)
......
package com.github.shadowsocks.database
import com.j256.ormlite.field.{DataType, DatabaseField}
/**
* @author Mygod
*/
object KeyValuePair {
val TYPE_UNINITIALIZED = 0
val TYPE_BOOLEAN = 1
val TYPE_FLOAT = 2
val TYPE_INT = 3
val TYPE_LONG = 4
val TYPE_STRING = 5
val TYPE_STRING_SET = 6
}
class KeyValuePair {
@DatabaseField(id = true)
var key: String = _
@DatabaseField
var valueType: Int = _
@DatabaseField(dataType = DataType.BYTE_ARRAY)
var value: Array[Byte] = _
def this(key: String, valueType: Int, value: Array[Byte]) = {
this()
this.key = key
this.valueType = valueType
this.value = value
}
}
...@@ -22,11 +22,11 @@ package com.github.shadowsocks.database ...@@ -22,11 +22,11 @@ package com.github.shadowsocks.database
import java.util.Locale import java.util.Locale
import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Binder import android.os.Binder
import android.util.Base64 import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.j256.ormlite.field.{DataType, DatabaseField} import com.j256.ormlite.field.{DataType, DatabaseField}
...@@ -108,37 +108,38 @@ class Profile { ...@@ -108,37 +108,38 @@ class Profile {
} }
override def toString: String = toUri.toString override def toString: String = toUri.toString
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor def serialize(store: OrmLitePreferenceDataStore) {
.putString(Key.name, name) store.putString(Key.name, name)
.putString(Key.host, host) store.putString(Key.host, host)
.putInt(Key.localPort, localPort) store.putInt(Key.localPort, localPort)
.putInt(Key.remotePort, remotePort) store.putInt(Key.remotePort, remotePort)
.putString(Key.password, password) store.putString(Key.password, password)
.putString(Key.route, route) store.putString(Key.route, route)
.putString(Key.remoteDns, remoteDns) store.putString(Key.remoteDns, remoteDns)
.putString(Key.method, method) store.putString(Key.method, method)
.putBoolean(Key.proxyApps, proxyApps) store.proxyApps = proxyApps
.putBoolean(Key.bypass, bypass) store.bypass = bypass
.putBoolean(Key.udpdns, udpdns) store.putBoolean(Key.udpdns, udpdns)
.putBoolean(Key.ipv6, ipv6) store.putBoolean(Key.ipv6, ipv6)
.putString(Key.individual, individual) store.individual = individual
.putString(Key.plugin, plugin) store.plugin = plugin
.remove(Key.dirty) store.remove(Key.dirty)
def deserialize(pref: SharedPreferences) { }
def deserialize(store: OrmLitePreferenceDataStore) {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = pref.getString(Key.name, null) name = store.getString(Key.name, null)
host = pref.getString(Key.host, null) host = store.getString(Key.host, null)
localPort = pref.getInt(Key.localPort, 0) localPort = store.getInt(Key.localPort, 0)
remotePort = pref.getInt(Key.remotePort, 0) remotePort = store.getInt(Key.remotePort, 0)
password = pref.getString(Key.password, null) password = store.getString(Key.password, null)
method = pref.getString(Key.method, null) method = store.getString(Key.method, null)
route = pref.getString(Key.route, null) route = store.getString(Key.route, null)
remoteDns = pref.getString(Key.remoteDns, null) remoteDns = store.getString(Key.remoteDns, null)
proxyApps = pref.getBoolean(Key.proxyApps, false) proxyApps = store.proxyApps
bypass = pref.getBoolean(Key.bypass, false) bypass = store.bypass
udpdns = pref.getBoolean(Key.udpdns, false) udpdns = store.getBoolean(Key.udpdns, false)
ipv6 = pref.getBoolean(Key.ipv6, false) ipv6 = store.getBoolean(Key.ipv6, false)
individual = pref.getString(Key.individual, null) individual = store.individual
plugin = pref.getString(Key.plugin, null) plugin = store.plugin
} }
} }
package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore
/**
* @author Mygod
*/
trait OnPreferenceDataStoreChangeListener {
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)
}
package com.github.shadowsocks.preference
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.util
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.{DBHelper, KeyValuePair}
import com.github.shadowsocks.utils.Key
import scala.collection.JavaConversions._
/**
* @author Mygod
*/
//noinspection AccessorLikeMethodIsUnit
final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDataStore {
import KeyValuePair._
override def getBoolean(key: String, defValue: Boolean = false): Boolean = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_BOOLEAN) ByteBuffer.wrap(pair.value).get() != 0 else defValue
case _ => defValue
}
override def getFloat(key: String, defValue: Float): Float = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_FLOAT) ByteBuffer.wrap(pair.value).getFloat() else defValue
case _ => defValue
}
override def getInt(key: String, defValue: Int): Int = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_INT) ByteBuffer.wrap(pair.value).getInt() else defValue
case _ => defValue
}
override def getLong(key: String, defValue: Long): Long = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_LONG) ByteBuffer.wrap(pair.value).getLong() else defValue
case _ => defValue
}
override def getString(key: String, defValue: String = null): String = dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair =>
if (pair.valueType == TYPE_STRING) new String(pair.value) else defValue
case _ => defValue
}
override def getStringSet(key: String, defValue: util.Set[String]): util.Set[String] =
dbHelper.kvPairDao.queryForId(key) match {
case pair: KeyValuePair => if (pair.valueType == TYPE_STRING_SET) {
val buffer = ByteBuffer.wrap(pair.value)
val result = new util.HashSet[String]()
while (buffer.hasRemaining) {
val chArr = new Array[Byte](buffer.getInt)
buffer.get(chArr)
result.add(new String(chArr))
}
result
} else defValue
case _ => defValue
}
override def putBoolean(key: String, value: Boolean) {
dbHelper.kvPairDao.createOrUpdate(
new KeyValuePair(key, TYPE_BOOLEAN, ByteBuffer.allocate(1).put((if (value) 1 else 0).toByte).array()))
fireChangeListener(key)
}
override def putFloat(key: String, value: Float) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_FLOAT, ByteBuffer.allocate(4).putFloat(value).array()))
fireChangeListener(key)
}
override def putInt(key: String, value: Int) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_INT, ByteBuffer.allocate(4).putInt(value).array()))
fireChangeListener(key)
}
override def putLong(key: String, value: Long) {
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_LONG, ByteBuffer.allocate(8).putLong(value).array()))
fireChangeListener(key)
}
override def putString(key: String, value: String) {
value match {
case null => remove(key)
case _ => dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_STRING, value.getBytes()))
}
fireChangeListener(key)
}
override def putStringSet(key: String, value: util.Set[String]) {
val stream = new ByteArrayOutputStream()
for (v <- value) {
stream.write(ByteBuffer.allocate(4).putInt(v.length).array())
stream.write(v.getBytes())
}
dbHelper.kvPairDao.createOrUpdate(new KeyValuePair(key, TYPE_STRING_SET, stream.toByteArray))
fireChangeListener(key)
}
def remove(key: String): Int = dbHelper.kvPairDao.deleteById(key)
private var listeners: Set[OnPreferenceDataStoreChangeListener] = Set.empty
private def fireChangeListener(key: String) = listeners.foreach(_.onPreferenceDataStoreChanged(this, key))
def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener
def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener
def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i)
def isNAT: Boolean = getBoolean(Key.isNAT)
def isNAT_=(value: Boolean): Unit = putBoolean(Key.isNAT, value)
def proxyApps: Boolean = getBoolean(Key.proxyApps)
def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
def bypass: Boolean = getBoolean(Key.bypass)
def bypass_=(value: Boolean): Unit = putBoolean(Key.bypass, value)
def individual: String = getString(Key.individual)
def individual_=(value: String): Unit = putString(Key.individual, value)
def plugin: String = getString(Key.plugin)
def plugin_=(value: String): Unit = putString(Key.plugin, value)
def dirty: Boolean = getBoolean(Key.dirty)
def dirty_=(value: Boolean): Unit = putBoolean(Key.dirty, value)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment