Unverified Commit e268719a authored by Max Lv's avatar Max Lv Committed by GitHub

Merge pull request #1794 from Mygod/room

Migrating to Room
parents 36c72e20 fa41164e
...@@ -10,6 +10,7 @@ buildscript { ...@@ -10,6 +10,7 @@ buildscript {
buildToolsVersion = '27.0.3' buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.1.1' supportLibraryVersion = '27.1.1'
takisoftFixVersion = '27.1.1.1' takisoftFixVersion = '27.1.1.1'
roomVersion = '1.1.0'
playServicesVersion = '12.0.1' playServicesVersion = '12.0.1'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.0.2' androidTestVersion = '1.0.2'
......
...@@ -4,6 +4,7 @@ import java.util.regex.Pattern ...@@ -4,6 +4,7 @@ import java.util.regex.Pattern
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
def getCurrentFlavor() { def getCurrentFlavor() {
String task = getGradle().getStartParameter().getTaskRequests().toString() String task = getGradle().getStartParameter().getTaskRequests().toString()
...@@ -26,6 +27,11 @@ android { ...@@ -26,6 +27,11 @@ android {
testApplicationId "com.github.shadowsocks.test" testApplicationId "com.github.shadowsocks.test"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW" resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
} }
buildTypes { buildTypes {
debug { debug {
...@@ -54,6 +60,7 @@ android { ...@@ -54,6 +60,7 @@ android {
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation "android.arch.persistence.room:runtime:$roomVersion"
implementation "com.android.support:customtabs:$supportLibraryVersion" implementation "com.android.support:customtabs:$supportLibraryVersion"
implementation "com.android.support:design:$supportLibraryVersion" implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion" implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
...@@ -63,7 +70,6 @@ dependencies { ...@@ -63,7 +70,6 @@ dependencies {
implementation "com.google.android.gms:play-services-analytics:$playServicesVersion" implementation "com.google.android.gms:play-services-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-vision:$playServicesVersion" implementation "com.google.android.gms:play-services-vision:$playServicesVersion"
implementation "com.google.firebase:firebase-config:$playServicesVersion" implementation "com.google.firebase:firebase-config:$playServicesVersion"
implementation 'com.j256.ormlite:ormlite-android:5.1'
implementation 'com.mikepenz:materialdrawer:6.0.7' implementation 'com.mikepenz:materialdrawer:6.0.7'
implementation 'com.squareup.okhttp3:okhttp:3.10.0' implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion" implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion"
...@@ -73,6 +79,8 @@ dependencies { ...@@ -73,6 +79,8 @@ dependencies {
implementation 'net.glxn.qrgen:android:2.0' implementation 'net.glxn.qrgen:android:2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3' implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
kapt "android.arch.persistence.room:compiler:$roomVersion"
testImplementation "android.arch.persistence.room:testing:$roomVersion"
testImplementation "junit:junit:$junitVersion" testImplementation "junit:junit:$junitVersion"
androidTestImplementation "com.android.support.test:runner:$androidTestVersion" androidTestImplementation "com.android.support.test:runner:$androidTestVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion" androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion"
......
{
"formatVersion": 1,
"database": {
"version": 26,
"identityHash": "a69accf94393d71abdb60873d4ea74a2",
"entities": [
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remotePort",
"columnName": "remotePort",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "method",
"columnName": "method",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "route",
"columnName": "route",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remoteDns",
"columnName": "remoteDns",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "proxyApps",
"columnName": "proxyApps",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypass",
"columnName": "bypass",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "udpdns",
"columnName": "udpdns",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ipv6",
"columnName": "ipv6",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "individual",
"columnName": "individual",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tx",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rx",
"columnName": "rx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userOrder",
"columnName": "userOrder",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "plugin",
"columnName": "plugin",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a69accf94393d71abdb60873d4ea74a2\")"
]
}
}
\ No newline at end of file
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "f1aab1fb633378621635c344dbc8ac7b",
"entities": [
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"f1aab1fb633378621635c344dbc8ac7b\")"
]
}
}
\ No newline at end of file
...@@ -2,7 +2,7 @@ package com.github.shadowsocks.aidl; ...@@ -2,7 +2,7 @@ package com.github.shadowsocks.aidl;
interface IShadowsocksServiceCallback { interface IShadowsocksServiceCallback {
oneway void stateChanged(int state, String profileName, String msg); oneway void stateChanged(int state, String profileName, String msg);
oneway void trafficUpdated(int profileId, long txRate, long rxRate, long txTotal, long rxTotal); oneway void trafficUpdated(long profileId, long txRate, long rxRate, long txTotal, long rxTotal);
// Traffic data has persisted to database, listener should refetch their data from database // Traffic data has persisted to database, listener should refetch their data from database
oneway void trafficPersisted(int profileId); oneway void trafficPersisted(long profileId);
} }
...@@ -57,7 +57,6 @@ import com.google.android.gms.analytics.StandardExceptionParser ...@@ -57,7 +57,6 @@ import com.google.android.gms.analytics.StandardExceptionParser
import com.google.android.gms.analytics.Tracker import com.google.android.gms.analytics.Tracker
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
...@@ -92,7 +91,7 @@ class App : Application() { ...@@ -92,7 +91,7 @@ class App : Application() {
val currentProfile: Profile? get() = val currentProfile: Profile? get() =
if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId) if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId)
fun switchProfile(id: Int): Profile { fun switchProfile(id: Long): Profile {
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile() val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
DataStore.profileId = result.id DataStore.profileId = result.id
return result return result
...@@ -116,7 +115,6 @@ class App : Application() { ...@@ -116,7 +115,6 @@ class App : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
app = this app = this
if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java, PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
......
...@@ -116,10 +116,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -116,10 +116,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun stateChanged(state: Int, profileName: String?, msg: String?) { override fun stateChanged(state: Int, profileName: String?, msg: String?) {
app.handler.post { changeState(state, msg, true) } app.handler.post { changeState(state, msg, true) }
} }
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) } app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) }
} }
override fun trafficPersisted(profileId: Int) { override fun trafficPersisted(profileId: Long) {
app.handler.post { ProfilesFragment.instance?.onTrafficPersisted(profileId) } app.handler.post { ProfilesFragment.instance?.onTrafficPersisted(profileId) }
} }
} }
...@@ -148,7 +148,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -148,7 +148,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state
stateListener?.invoke(state) stateListener?.invoke(state)
} }
fun updateTraffic(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { fun updateTraffic(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
txText.text = Formatter.formatFileSize(this, txTotal) txText.text = Formatter.formatFileSize(this, txTotal)
rxText.text = Formatter.formatFileSize(this, rxTotal) rxText.text = Formatter.formatFileSize(this, rxTotal)
txRateText.text = getString(R.string.speed, Formatter.formatFileSize(this, txRate)) txRateText.text = getString(R.string.speed, Formatter.formatFileSize(this, txRate))
......
...@@ -56,7 +56,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -56,7 +56,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
} }
private var profileId = -1 private var profileId = -1L
private lateinit var isProxyApps: SwitchPreference private lateinit var isProxyApps: SwitchPreference
private lateinit var plugin: IconListPreference private lateinit var plugin: IconListPreference
private lateinit var pluginConfigure: EditTextPreference private lateinit var pluginConfigure: EditTextPreference
...@@ -66,7 +66,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -66,7 +66,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.privateStore preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = requireActivity() val activity = requireActivity()
profileId = activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1) profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L)
addPreferencesFromResource(R.xml.pref_profile) addPreferencesFromResource(R.xml.pref_profile)
if (Build.VERSION.SDK_INT >= 25 && activity.getSystemService(UserManager::class.java).isDemoUser) { if (Build.VERSION.SDK_INT >= 25 && activity.getSystemService(UserManager::class.java).isDemoUser) {
findPreference(Key.host).summary = "shadowsocks.example.org" findPreference(Key.host).summary = "shadowsocks.example.org"
......
...@@ -68,7 +68,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -68,7 +68,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
BaseService.CONNECTED, BaseService.STOPPED -> true BaseService.CONNECTED, BaseService.STOPPED -> true
else -> false else -> false
} }
private fun isProfileEditable(id: Int) = when ((activity as MainActivity).state) { private fun isProfileEditable(id: Long) = when ((activity as MainActivity).state) {
BaseService.CONNECTED -> id != DataStore.profileId BaseService.CONNECTED -> id != DataStore.profileId
BaseService.STOPPED -> true BaseService.STOPPED -> true
else -> false else -> false
...@@ -281,17 +281,17 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -281,17 +281,17 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
for ((_, item) in actions) ProfileManager.delProfile(item.id) for ((_, item) in actions) ProfileManager.delProfile(item.id)
} }
fun refreshId(id: Int) { fun refreshId(id: Long) {
val index = profiles.indexOfFirst { it.id == id } val index = profiles.indexOfFirst { it.id == id }
if (index >= 0) notifyItemChanged(index) if (index >= 0) notifyItemChanged(index)
} }
fun deepRefreshId(id: Int) { fun deepRefreshId(id: Long) {
val index = profiles.indexOfFirst { it.id == id } val index = profiles.indexOfFirst { it.id == id }
if (index < 0) return if (index < 0) return
profiles[index] = ProfileManager.getProfile(id)!! profiles[index] = ProfileManager.getProfile(id)!!
notifyItemChanged(index) notifyItemChanged(index)
} }
fun removeId(id: Int) { fun removeId(id: Long) {
val index = profiles.indexOfFirst { it.id == id } val index = profiles.indexOfFirst { it.id == id }
if (index < 0) return if (index < 0) return
profiles.removeAt(index) profiles.removeAt(index)
...@@ -304,7 +304,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -304,7 +304,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
val profilesAdapter by lazy { ProfilesAdapter() } val profilesAdapter by lazy { ProfilesAdapter() }
private lateinit var undoManager: UndoSnackbarManager<Profile> private lateinit var undoManager: UndoSnackbarManager<Profile>
private var bandwidthProfile: Int = 0 private var bandwidthProfile = 0L
private var txTotal: Long = 0L private var txTotal: Long = 0L
private var rxTotal: Long = 0L private var rxTotal: Long = 0L
...@@ -324,8 +324,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -324,8 +324,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
toolbar.inflateMenu(R.menu.profile_manager_menu) toolbar.inflateMenu(R.menu.profile_manager_menu)
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
if (ProfileManager.getFirstProfile() == null) if (!ProfileManager.isNotEmpty()) DataStore.profileId = ProfileManager.createProfile().id
DataStore.profileId = ProfileManager.createProfile().id
val profilesList = view.findViewById<RecyclerView>(R.id.list) val profilesList = view.findViewById<RecyclerView>(R.id.list)
val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
profilesList.layoutManager = layoutManager profilesList.layoutManager = layoutManager
...@@ -378,7 +377,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -378,7 +377,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
Snackbar.LENGTH_LONG).show() Snackbar.LENGTH_LONG).show()
return true return true
} }
} catch (_: Exception) { } } catch (exc: Exception) {
exc.printStackTrace()
}
Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_err,
Snackbar.LENGTH_LONG).show() Snackbar.LENGTH_LONG).show()
true true
...@@ -399,8 +400,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -399,8 +400,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
} }
override fun onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { override fun onTrafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
if (profileId != -1) { // ignore resets from MainActivity if (profileId != -1L) { // ignore resets from MainActivity
if (bandwidthProfile != profileId) { if (bandwidthProfile != profileId) {
onTrafficPersisted(bandwidthProfile) onTrafficPersisted(bandwidthProfile)
bandwidthProfile = profileId bandwidthProfile = profileId
...@@ -410,7 +411,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -410,7 +411,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.refreshId(profileId) profilesAdapter.refreshId(profileId)
} }
} }
fun onTrafficPersisted(profileId: Int) { fun onTrafficPersisted(profileId: Long) {
txTotal = 0 txTotal = 0
rxTotal = 0 rxTotal = 0
if (bandwidthProfile != profileId) { if (bandwidthProfile != profileId) {
......
...@@ -38,7 +38,7 @@ open class ToolbarFragment : Fragment() { ...@@ -38,7 +38,7 @@ open class ToolbarFragment : Fragment() {
activity.drawer.setToolbar(activity, toolbar, true) activity.drawer.setToolbar(activity, toolbar, true)
} }
open fun onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { } open fun onTrafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { }
open fun onBackPressed(): Boolean = false open fun onBackPressed(): Boolean = false
} }
...@@ -54,7 +54,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -54,7 +54,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
private val callback by lazy { private val callback by lazy {
object : IShadowsocksServiceCallback.Stub() { object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
service as Context service as Context
val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, txRate)) val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, txRate))
val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, rxRate)) val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, rxRate))
...@@ -63,7 +63,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -63,7 +63,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
Formatter.formatFileSize(service, txTotal), Formatter.formatFileSize(service, rxTotal))) Formatter.formatFileSize(service, txTotal), Formatter.formatFileSize(service, rxTotal)))
show() show()
} }
override fun trafficPersisted(profileId: Int) { } override fun trafficPersisted(profileId: Long) { }
} }
} }
private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) } private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) }
......
...@@ -64,8 +64,8 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface { ...@@ -64,8 +64,8 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface {
tile.label = label ?: getString(R.string.app_name) tile.label = label ?: getString(R.string.app_name)
tile.updateTile() tile.updateTile()
} }
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { } override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { }
override fun trafficPersisted(profileId: Int) { } override fun trafficPersisted(profileId: Long) { }
} }
} }
......
...@@ -20,37 +20,54 @@ ...@@ -20,37 +20,54 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import com.j256.ormlite.field.DataType import android.arch.persistence.room.*
import com.j256.ormlite.field.DatabaseField
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
@Entity
class KeyValuePair() { class KeyValuePair() {
companion object { companion object {
const val TYPE_UNINITIALIZED = 0 const val TYPE_UNINITIALIZED = 0
const val TYPE_BOOLEAN = 1 const val TYPE_BOOLEAN = 1
const val TYPE_FLOAT = 2 const val TYPE_FLOAT = 2
@Deprecated("Use TYPE_LONG.")
const val TYPE_INT = 3 const val TYPE_INT = 3
const val TYPE_LONG = 4 const val TYPE_LONG = 4
const val TYPE_STRING = 5 const val TYPE_STRING = 5
const val TYPE_STRING_SET = 6 const val TYPE_STRING_SET = 6
} }
@DatabaseField(id = true) @android.arch.persistence.room.Dao
var key: String? = "" interface Dao {
@DatabaseField @Query("SELECT * FROM `KeyValuePair` WHERE `key` = :key")
operator fun get(key: String): KeyValuePair?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun put(value: KeyValuePair): Long
@Query("DELETE FROM `KeyValuePair` WHERE `key` = :key")
fun delete(key: String): Int
}
@PrimaryKey
var key: String = ""
var valueType: Int = TYPE_UNINITIALIZED var valueType: Int = TYPE_UNINITIALIZED
@DatabaseField(dataType = DataType.BYTE_ARRAY)
var value: ByteArray = ByteArray(0) var value: ByteArray = ByteArray(0)
val boolean: Boolean? val boolean: Boolean?
get() = if (valueType == TYPE_BOOLEAN) ByteBuffer.wrap(value).get() != 0.toByte() else null get() = if (valueType == TYPE_BOOLEAN) ByteBuffer.wrap(value).get() != 0.toByte() else null
val float: Float? val float: Float?
get() = if (valueType == TYPE_FLOAT) ByteBuffer.wrap(value).float else null get() = if (valueType == TYPE_FLOAT) ByteBuffer.wrap(value).float else null
@Suppress("DEPRECATION")
@Deprecated("Use long.", ReplaceWith("long"))
val int: Int? val int: Int?
get() = if (valueType == TYPE_INT) ByteBuffer.wrap(value).int else null get() = if (valueType == TYPE_INT) ByteBuffer.wrap(value).int else null
val long: Long? val long: Long? get() = when (valueType) {
get() = if (valueType == TYPE_LONG) ByteBuffer.wrap(value).long else null @Suppress("DEPRECATION")
TYPE_INT -> ByteBuffer.wrap(value).int.toLong()
TYPE_LONG -> ByteBuffer.wrap(value).long
else -> null
}
val string: String? val string: String?
get() = if (valueType == TYPE_STRING) String(value) else null get() = if (valueType == TYPE_STRING) String(value) else null
val stringSet: Set<String>? val stringSet: Set<String>?
...@@ -65,7 +82,8 @@ class KeyValuePair() { ...@@ -65,7 +82,8 @@ class KeyValuePair() {
result result
} else null } else null
constructor(key: String?) : this() { @Ignore
constructor(key: String) : this() {
this.key = key this.key = key
} }
...@@ -80,6 +98,8 @@ class KeyValuePair() { ...@@ -80,6 +98,8 @@ class KeyValuePair() {
this.value = ByteBuffer.allocate(4).putFloat(value).array() this.value = ByteBuffer.allocate(4).putFloat(value).array()
return this return this
} }
@Suppress("DEPRECATION")
@Deprecated("Use long.")
fun put(value: Int): KeyValuePair { fun put(value: Int): KeyValuePair {
valueType = TYPE_INT valueType = TYPE_INT
this.value = ByteBuffer.allocate(4).putInt(value).array() this.value = ByteBuffer.allocate(4).putInt(value).array()
......
...@@ -20,137 +20,39 @@ ...@@ -20,137 +20,39 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.database.sqlite.SQLiteDatabase import android.arch.persistence.db.SupportSQLiteDatabase
import android.support.v7.preference.PreferenceManager import android.arch.persistence.room.Database
import android.text.TextUtils import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.migration.RecreateSchemaMigration
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.AndroidDatabaseConnection
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) { @Database(entities = [Profile::class, KeyValuePair::class], version = 26)
@Suppress("UNCHECKED_CAST") abstract class PrivateDatabase : RoomDatabase() {
val profileDao: Dao<Profile, Int> by lazy { getDao(Profile::class.java) as Dao<Profile, Int> } companion object {
@Suppress("UNCHECKED_CAST") private val instance by lazy {
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> } Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PROFILE)
.addMigrations(
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) { Migration26
TableUtils.createTable(connectionSource, Profile::class.java) )
TableUtils.createTable(connectionSource, KeyValuePair::class.java) .fallbackToDestructiveMigration()
} .allowMainThreadQueries()
.build()
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.dropTable<Profile, Int>(connectionSource, Profile::class.java, true)
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
onCreate(database, connectionSource)
}
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) {
if (oldVersion < 7) {
recreate(database, connectionSource)
return
}
try {
if (oldVersion < 8) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
}
if (oldVersion < 9) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
} else if (oldVersion < 19) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
}
if (oldVersion < 11) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
}
if (oldVersion < 12) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," +
" proxyApps, bypass, udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," +
" bypass, udpdns, ipv6, individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
} }
if (oldVersion < 15) { val profileDao get() = instance.profileDao()
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") val kvPairDao get() = instance.keyValuePairDao()
var i = 0L
val apps by lazy { app.packageManager.getInstalledApplications(0) }
for (profile in profileDao.queryForAll()) {
if (oldVersion < 14) {
val uidSet = profile.individual.split('|').filter(TextUtils::isDigitsOnly)
.map(String::toInt).toSet()
profile.individual = apps.filter { uidSet.contains(it.uid) }
.joinToString("\n") { it.packageName }
}
profile.userOrder = i
profileDao.update(profile)
i += 1
}
} }
abstract fun profileDao(): Profile.Dao
abstract fun keyValuePairDao(): KeyValuePair.Dao
if (oldVersion < 16) { private object Migration26 : RecreateSchemaMigration(25, 26, "Profile",
profileDao.executeRawNoArgs( "(`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT)",
"UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") "`id`, `name`, `host`, `remotePort`, `password`, `method`, `route`, `remoteDns`, `proxyApps`, `bypass`, `udpdns`, `ipv6`, `individual`, `tx`, `rx`, `userOrder`, `plugin`") {
override fun migrate(database: SupportSQLiteDatabase) {
super.migrate(database)
PublicDatabase.Migration3.migrate(database)
} }
if (oldVersion < 21) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
}
if (oldVersion < 17) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
} else if (oldVersion < 22) {
// upgrade kcptun to SIP003 plugin
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"plugin) " +
"SELECT id, name, host, localPort, " +
"CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
}
if (oldVersion < 23) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
profileDao.executeRawNoArgs("COMMIT;")
val old = PreferenceManager.getDefaultSharedPreferences(app)
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
}
if (oldVersion < 25) {
PublicDatabase.onUpgrade(database, 0, -1)
}
} catch (ex: Exception) {
app.track(ex)
recreate(database, connectionSource)
}
}
override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
val connection = AndroidDatabaseConnection(db, true)
connectionSource.saveSpecialConnection(connection)
recreate(db, connectionSource)
connectionSource.clearSpecialConnection(connection)
} }
} }
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.arch.persistence.room.*
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
...@@ -27,13 +28,12 @@ import com.github.shadowsocks.plugin.PluginConfiguration ...@@ -27,13 +28,12 @@ import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import com.j256.ormlite.field.DataType
import com.j256.ormlite.field.DatabaseField
import java.io.Serializable import java.io.Serializable
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.util.* import java.util.*
@Entity
class Profile : Serializable { class Profile : Serializable {
companion object { companion object {
private const val TAG = "ShadowParser" private const val TAG = "ShadowParser"
...@@ -93,61 +93,50 @@ class Profile : Serializable { ...@@ -93,61 +93,50 @@ class Profile : Serializable {
}.filterNotNull() }.filterNotNull()
} }
@DatabaseField(generatedId = true) @android.arch.persistence.room.Dao
var id: Int = 0 interface Dao {
@Query("SELECT * FROM `Profile` WHERE `id` = :id")
operator fun get(id: Long): Profile?
@DatabaseField @Query("SELECT * FROM `Profile` ORDER BY `userOrder`")
var name: String? = "" fun list(): List<Profile>
@DatabaseField @Query("SELECT MAX(`userOrder`) + 1 FROM `Profile`")
var host: String = "198.199.101.152" fun nextOrder(): Long?
@DatabaseField @Query("SELECT 1 FROM `Profile` LIMIT 1")
var remotePort: Int = 8388 fun isNotEmpty(): Boolean
@DatabaseField @Insert
var password: String = "u1rRWTssNv0p" fun create(value: Profile): Long
@DatabaseField @Update
var method: String = "aes-256-cfb" fun update(value: Profile): Int
@DatabaseField @Query("DELETE FROM `Profile` WHERE `id` = :id")
var route: String = "all" fun delete(id: Long): Int
}
@DatabaseField @PrimaryKey(autoGenerate = true)
var id: Long = 0
var name: String? = ""
var host: String = "198.199.101.152"
var remotePort: Int = 8388
var password: String = "u1rRWTssNv0p"
var method: String = "aes-256-cfb"
var route: String = "all"
var remoteDns: String = "8.8.8.8" var remoteDns: String = "8.8.8.8"
@DatabaseField
var proxyApps: Boolean = false var proxyApps: Boolean = false
@DatabaseField
var bypass: Boolean = false var bypass: Boolean = false
@DatabaseField
var udpdns: Boolean = false var udpdns: Boolean = false
@DatabaseField
var ipv6: Boolean = true var ipv6: Boolean = true
@DatabaseField(dataType = DataType.LONG_STRING)
var individual: String = "" var individual: String = ""
@DatabaseField
var tx: Long = 0 var tx: Long = 0
@DatabaseField
var rx: Long = 0 var rx: Long = 0
@DatabaseField
val date: Date = Date()
@DatabaseField
var userOrder: Long = 0 var userOrder: Long = 0
@DatabaseField
var plugin: String? = null var plugin: String? = null
// not persisted in db, only used by direct boot @Ignore // not persisted in db, only used by direct boot
var dirty: Boolean = false var dirty: Boolean = false
val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort) val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort)
......
...@@ -50,10 +50,8 @@ object ProfileManager { ...@@ -50,10 +50,8 @@ object ProfileManager {
profile.individual = oldProfile.individual profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns profile.udpdns = oldProfile.udpdns
} }
val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder() profile.userOrder = PrivateDatabase.profileDao.nextOrder() ?: 0
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult profile.id = PrivateDatabase.profileDao.create(profile)
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
PrivateDatabase.profileDao.createOrUpdate(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile) ProfilesFragment.instance?.profilesAdapter?.add(profile)
return profile return profile
} }
...@@ -62,11 +60,11 @@ object ProfileManager { ...@@ -62,11 +60,11 @@ object ProfileManager {
* Note: It's caller's responsibility to update DirectBoot profile if necessary. * Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/ */
@Throws(SQLException::class) @Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile) fun updateProfile(profile: Profile) = check(PrivateDatabase.profileDao.update(profile) == 1)
@Throws(IOException::class) @Throws(IOException::class)
fun getProfile(id: Int): Profile? = try { fun getProfile(id: Long): Profile? = try {
PrivateDatabase.profileDao.queryForId(id) PrivateDatabase.profileDao[id]
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex) if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex) Log.e(TAG, "getProfile", ex)
...@@ -75,25 +73,25 @@ object ProfileManager { ...@@ -75,25 +73,25 @@ object ProfileManager {
} }
@Throws(SQLException::class) @Throws(SQLException::class)
fun delProfile(id: Int) { fun delProfile(id: Long) {
PrivateDatabase.profileDao.deleteById(id) check(PrivateDatabase.profileDao.delete(id) == 1)
ProfilesFragment.instance?.profilesAdapter?.removeId(id) ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean() if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
} }
@Throws(IOException::class) @Throws(IOException::class)
fun getFirstProfile(): Profile? = try { fun isNotEmpty(): Boolean = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull() PrivateDatabase.profileDao.isNotEmpty()
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex) if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getFirstProfile", ex) Log.e(TAG, "isNotEmpty", ex)
app.track(ex) app.track(ex)
null false
} }
@Throws(IOException::class) @Throws(IOException::class)
fun getAllProfiles(): List<Profile>? = try { fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare()) PrivateDatabase.profileDao.list()
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex) if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex) Log.e(TAG, "getAllProfiles", ex)
......
...@@ -20,42 +20,31 @@ ...@@ -20,42 +20,31 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.database.sqlite.SQLiteDatabase import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.database.migration.RecreateSchemaMigration
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.AndroidDatabaseConnection
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC, null, 2) { @Database(entities = [(KeyValuePair::class)], version = 3)
@Suppress("UNCHECKED_CAST") abstract class PublicDatabase : RoomDatabase() {
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> } companion object {
private val instance by lazy {
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) { Room.databaseBuilder(app.deviceContext, PublicDatabase::class.java, Key.DB_PUBLIC)
TableUtils.createTable(connectionSource, KeyValuePair::class.java) .allowMainThreadQueries()
.addMigrations(
Migration3
)
.fallbackToDestructiveMigration()
.build()
} }
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?, val kvPairDao get() = instance.keyValuePairDao()
oldVersion: Int, newVersion: Int) {
if (oldVersion < 1) {
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
.forEach { kvPairDao.createOrUpdate(it) }
} }
abstract fun keyValuePairDao(): KeyValuePair.Dao
if (oldVersion < 2) { internal object Migration3 : RecreateSchemaMigration(2, 3, "KeyValuePair",
kvPairDao.createOrUpdate(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString())) "(`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
} "`key`, `valueType`, `value`")
}
override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
val connection = AndroidDatabaseConnection(db, true)
connectionSource.saveSpecialConnection(connection)
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
onCreate(db, connectionSource)
connectionSource.clearSpecialConnection(connection)
}
} }
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2018 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.database.migration
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.migration.Migration
open class RecreateSchemaMigration(oldVersion: Int, newVersion: Int, private val table: String,
private val schema: String, private val keys: String) :
Migration(oldVersion, newVersion) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `tmp` $schema")
database.execSQL("INSERT INTO `tmp` ($keys) SELECT $keys FROM `$table`")
database.execSQL("DROP TABLE `$table`")
database.execSQL("ALTER TABLE `tmp` RENAME TO `$table`")
}
}
...@@ -29,9 +29,9 @@ import com.github.shadowsocks.utils.Key ...@@ -29,9 +29,9 @@ import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
object DataStore { object DataStore {
val publicStore = OrmLitePreferenceDataStore(PublicDatabase.kvPairDao) val publicStore = RoomPreferenceDataStore(PublicDatabase.kvPairDao)
// privateStore will only be used as temp storage for ProfileConfigFragment // privateStore will only be used as temp storage for ProfileConfigFragment
val privateStore = OrmLitePreferenceDataStore(PrivateDatabase.kvPairDao) val privateStore = RoomPreferenceDataStore(PrivateDatabase.kvPairDao)
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() } private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() }
...@@ -43,10 +43,10 @@ object DataStore { ...@@ -43,10 +43,10 @@ object DataStore {
} else parsePort(publicStore.getString(key), default + userIndex) } else parsePort(publicStore.getString(key), default + userIndex)
} }
var profileId: Int var profileId: Long
get() = publicStore.getInt(Key.id) ?: 0 get() = publicStore.getLong(Key.id) ?: 0
set(value) { set(value) {
publicStore.putInt(Key.id, value) publicStore.putLong(Key.id, value)
if (DataStore.directBootAware) DirectBoot.update() if (DataStore.directBootAware) DirectBoot.update()
} }
val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true
......
...@@ -22,61 +22,60 @@ package com.github.shadowsocks.preference ...@@ -22,61 +22,60 @@ package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.KeyValuePair import com.github.shadowsocks.database.KeyValuePair
import com.j256.ormlite.dao.Dao
import java.util.HashSet import java.util.HashSet
@Suppress("MemberVisibilityCanPrivate", "unused") @Suppress("MemberVisibilityCanBePrivate", "unused")
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() { open class RoomPreferenceDataStore(private val kvPairDao: KeyValuePair.Dao) : PreferenceDataStore() {
fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean fun getBoolean(key: String) = kvPairDao[key]?.boolean
fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float fun getFloat(key: String) = kvPairDao[key]?.float
fun getInt(key: String?) = kvPairDao.queryForId(key)?.int fun getInt(key: String) = kvPairDao[key]?.long?.toInt()
fun getLong(key: String?) = kvPairDao.queryForId(key)?.long fun getLong(key: String) = kvPairDao[key]?.long
fun getString(key: String?) = kvPairDao.queryForId(key)?.string fun getString(key: String) = kvPairDao[key]?.string
fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet fun getStringSet(key: String) = kvPairDao[key]?.stringSet
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue override fun getBoolean(key: String, defValue: Boolean) = getBoolean(key) ?: defValue
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue override fun getFloat(key: String, defValue: Float) = getFloat(key) ?: defValue
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue override fun getInt(key: String, defValue: Int) = getInt(key) ?: defValue
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue override fun getLong(key: String, defValue: Long) = getLong(key) ?: defValue
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue override fun getString(key: String, defValue: String?) = getString(key) ?: defValue
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(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 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 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 putInt(key: String, value: Int?) = if (value == null) remove(key) else putLong(key, value.toLong())
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value) fun putLong(key: String, value: Long?) = if (value == null) remove(key) else putLong(key, value)
override fun putBoolean(key: String?, value: Boolean) { override fun putBoolean(key: String, value: Boolean) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value)) kvPairDao.put(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putFloat(key: String?, value: Float) { override fun putFloat(key: String, value: Float) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value)) kvPairDao.put(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putInt(key: String?, value: Int) { override fun putInt(key: String, value: Int) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value)) kvPairDao.put(KeyValuePair(key).put(value.toLong()))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putLong(key: String?, value: Long) { override fun putLong(key: String, value: Long) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value)) kvPairDao.put(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else { override fun putString(key: String, value: String?) = if (value == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value)) kvPairDao.put(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else { override fun putStringSet(key: String, values: MutableSet<String>?) = if (values == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(values)) kvPairDao.put(KeyValuePair(key).put(values))
fireChangeListener(key) fireChangeListener(key)
} }
fun remove(key: String?) { fun remove(key: String) {
kvPairDao.deleteById(key) kvPairDao.delete(key)
fireChangeListener(key) fireChangeListener(key)
} }
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>() private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) } private fun fireChangeListener(key: String) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener) fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener) fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
} }
...@@ -36,12 +36,17 @@ class Settings(bundle: Bundle?) { ...@@ -36,12 +36,17 @@ class Settings(bundle: Bundle?) {
} }
var switchOn: Boolean = bundle?.getBoolean(KEY_SWITCH_ON, true) ?: true var switchOn: Boolean = bundle?.getBoolean(KEY_SWITCH_ON, true) ?: true
var profileId: Int = bundle?.getInt(KEY_PROFILE_ID, -1) ?: -1 var profileId: Long
init {
profileId = bundle?.getLong(KEY_PROFILE_ID, -1L) ?: -1L
if (profileId < 0) profileId = (bundle?.getInt(KEY_PROFILE_ID, -1) ?: -1).toLong()
}
fun toIntent(context: Context): Intent { fun toIntent(context: Context): Intent {
val bundle = Bundle() val bundle = Bundle()
if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false) if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false)
if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId) if (profileId >= 0) bundle.putLong(KEY_PROFILE_ID, profileId)
val profile = ProfileManager.getProfile(profileId) val profile = ProfileManager.getProfile(profileId)
return Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB, return Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
if (profile != null) context.getString(if (switchOn) R.string.start_service else R.string.stop_service, if (profile != null) context.getString(if (switchOn) R.string.start_service else R.string.stop_service,
......
...@@ -29,7 +29,8 @@ class KeyValuePairTest { ...@@ -29,7 +29,8 @@ class KeyValuePairTest {
val kvp = KeyValuePair() val kvp = KeyValuePair()
Assert.assertEquals(true, kvp.put(true).boolean) Assert.assertEquals(true, kvp.put(true).boolean)
Assert.assertEquals(3f, kvp.put(3f).float) Assert.assertEquals(3f, kvp.put(3f).float)
Assert.assertEquals(3, kvp.put(3).int) @Suppress("DEPRECATION")
Assert.assertEquals(3L, kvp.put(3).long)
Assert.assertEquals(3L, kvp.put(3L).long) Assert.assertEquals(3L, kvp.put(3L).long)
Assert.assertEquals("3", kvp.put("3").string) Assert.assertEquals("3", kvp.put("3").string)
val set = (0 until 3).map(Int::toString).toSet() val set = (0 until 3).map(Int::toString).toSet()
......
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