Unverified Commit 612a34a6 authored by Max Lv's avatar Max Lv Committed by GitHub

Merge pull request #1508 from Mygod/direct-boot

Implement direct boot awareness
parents 6eec78a3 f1934f87
......@@ -113,6 +113,7 @@
<service
android:name=".bg.VpnService"
android:process=":bg"
android:directBootAware="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
......@@ -124,17 +125,20 @@
<service
android:name=".bg.TransproxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>
<service
android:name=".bg.ProxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>
<service android:name=".bg.TileService" android:label="@string/quick_toggle"
android:process=":bg"
android:directBootAware="true"
android:icon="@drawable/ic_start_connected"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
......@@ -142,13 +146,20 @@
</intent-filter>
</service>
<receiver android:name=".BootReceiver" android:process=":bg" android:enabled="false">
<receiver android:name=".BootReceiver"
android:process=":bg"
android:directBootAware="true"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".tasker.ActionListener" android:process=":bg" tools:ignore="ExportedReceiver">
<receiver android:name=".tasker.ActionListener"
android:process=":bg"
android:directBootAware="true"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
......@@ -156,16 +167,22 @@
<!-- android-job components -->
<service android:name="com.evernote.android.job.v21.PlatformJobService"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmService"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.JobRescheduleService"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"/>
android:process=":bg"
android:directBootAware="true"/>
</application>
</manifest>
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.evernote.android.job
object JobConstants {
const val DATABASE_NAME = JobStorage.DATABASE_NAME
const val PREF_FILE_NAME = JobStorage.PREF_FILE_NAME
}
......@@ -24,6 +24,7 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
......@@ -34,9 +35,12 @@ import android.os.Handler
import android.os.LocaleList
import android.os.Looper
import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import com.evernote.android.job.JobConstants
import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.AclSyncJob
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
......@@ -44,10 +48,7 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen
import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.*
import com.google.android.gms.analytics.GoogleAnalytics
import com.google.android.gms.analytics.HitBuilders
import com.google.android.gms.analytics.StandardExceptionParser
......@@ -75,8 +76,9 @@ class App : Application() {
}
val handler by lazy { Handler(Looper.getMainLooper()) }
val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
val info: PackageInfo by lazy { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) }
fun startService() {
......@@ -86,7 +88,8 @@ class App : Application() {
fun reloadService() = sendBroadcast(Intent(Action.RELOAD))
fun stopService() = sendBroadcast(Intent(Action.CLOSE))
val currentProfile: Profile? get() = ProfileManager.getProfile(DataStore.profileId)
val currentProfile: Profile? get() =
if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId)
fun switchProfile(id: Int): Profile {
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
......@@ -162,31 +165,42 @@ class App : Application() {
if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(resources.configuration)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
if (Build.VERSION.SDK_INT >= 24) { // migrate old files
deviceContext.moveDatabaseFrom(this, Key.DB_PUBLIC)
deviceContext.moveDatabaseFrom(this, JobConstants.DATABASE_NAME)
deviceContext.moveSharedPreferencesFrom(this, JobConstants.PREF_FILE_NAME)
val old = Acl.getFile(Acl.CUSTOM_RULES, this)
if (old.canRead()) {
Acl.getFile(Acl.CUSTOM_RULES).writeText(old.readText())
old.delete()
}
}
FirebaseApp.initializeApp(this)
FirebaseApp.initializeApp(deviceContext)
remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")
}
JobManager.create(deviceContext).addJobCreator(AclSyncJob)
JobManager.create(this).addJobCreator(AclSyncJob)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
TcpFastOpen.enabled(DataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
// handle data restored
if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update()
TcpFastOpen.enabled(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets
for (dir in arrayOf("acl", "overture"))
try {
for (file in assetManager.list(dir)) assetManager.open(dir + '/' + file).use { input ->
File(filesDir, file).outputStream().use { output -> input.copyTo(output) }
File(deviceContext.filesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
Log.e(TAG, e.message)
app.track(e)
}
DataStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
DataStore.publicStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
}
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
......
......@@ -47,6 +47,7 @@ import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.thread
import java.util.concurrent.atomic.AtomicBoolean
......@@ -231,6 +232,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
it.individual = proxiedAppString
ProfileManager.updateProfile(it)
}
if (DataStore.directBootAware) DirectBoot.update()
Toast.makeText(this, R.string.action_apply_all, Toast.LENGTH_SHORT).show()
} else Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show()
return true
......
......@@ -26,6 +26,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore
class BootReceiver : BroadcastReceiver() {
companion object {
......@@ -39,6 +40,11 @@ class BootReceiver : BroadcastReceiver() {
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) app.startService()
val locked = when (intent.action) {
Intent.ACTION_BOOT_COMPLETED -> false
Intent.ACTION_LOCKED_BOOT_COMPLETED -> true // constant will be folded so no need to do version checks
else -> return
}
if (DataStore.directBootAware == locked) app.startService()
}
}
......@@ -27,6 +27,6 @@ import com.github.shadowsocks.utils.Key
@Deprecated("Only used in API level < 23. For 6.0+, Auto Backup for Apps is used.")
class ConfigBackupHelper : BackupAgentHelper() {
override fun onCreate() = addHelper("com.github.shadowsocks.database.profile",
FileBackupHelper(this, "../databases/" + Key.PROFILE, Acl.CUSTOM_RULES + ".acl"))
override fun onCreate() = addHelper("com.github.shadowsocks.database.profile", FileBackupHelper(this,
"../databases/" + Key.DB_PROFILE, "../databases/" + Key.DB_PUBLIC, Acl.CUSTOM_RULES + ".acl"))
}
......@@ -20,27 +20,40 @@
package com.github.shadowsocks
import android.app.admin.DevicePolicyManager
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore
preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect) as SwitchPreference
switch.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
val boot = findPreference(Key.isAutoConnect) as SwitchPreference
val directBootAwareListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true
}
switch.isChecked = BootReceiver.enabled
boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
directBootAwareListener.onPreferenceChange(null, DataStore.directBootAware)
}
boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && context!!.getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER)
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba)
val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled
......
......@@ -335,7 +335,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
changeState(BaseService.IDLE) // reset everything to init state
app.handler.post { connection.connect() }
DataStore.registerChangeListener(this)
DataStore.publicStore.registerChangeListener(this)
val intent = this.intent
if (intent != null) handleShareIntent(intent)
......@@ -434,7 +434,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onDestroy() {
super.onDestroy()
DataStore.unregisterChangeListener(this)
DataStore.publicStore.unregisterChangeListener(this)
connection.disconnect()
BackupManager(this).dataChanged()
app.handler.removeCallbacksAndMessages(null)
......
......@@ -45,6 +45,7 @@ import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PluginConfigurationDialogFragment
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.takisoft.fix.support.v7.preference.EditTextPreference
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
......@@ -62,7 +63,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
private lateinit var pluginConfiguration: PluginConfiguration
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore
preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = activity!!
val profile = ProfileManager.getProfile(activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1))
if (profile == null) {
......@@ -102,7 +103,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
pluginConfigure.onPreferenceChangeListener = this
initPlugins()
app.listenForPackageChanges { initPlugins() }
DataStore.registerChangeListener(this)
DataStore.privateStore.registerChangeListener(this)
}
private fun initPlugins() {
......@@ -130,6 +131,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
profile.deserialize()
ProfileManager.updateProfile(profile)
ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profile.id)
if (DataStore.profileId == profile.id && DataStore.directBootAware) DirectBoot.update()
activity!!.finish()
}
......@@ -201,7 +203,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
}
override fun onDestroy() {
DataStore.unregisterChangeListener(this)
DataStore.privateStore.unregisterChangeListener(this)
super.onDestroy()
}
}
......@@ -20,6 +20,7 @@
package com.github.shadowsocks.acl
import android.content.Context
import android.support.v7.util.SortedList
import android.util.Log
import com.github.shadowsocks.App.Companion.app
......@@ -45,7 +46,7 @@ class Acl {
val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex()
fun getFile(id: String) = File(app.filesDir, id + ".acl")
fun getFile(id: String, context: Context = app.deviceContext) = File(context.filesDir, id + ".acl")
val customRules: Acl get() {
val acl = Acl()
......@@ -56,7 +57,7 @@ class Acl {
acl.bypassHostnames.clear() // everything is bypassed
return acl
}
fun save(id: String, acl: Acl): Unit = getFile(id).bufferedWriter().use { it.write(acl.toString()) }
fun save(id: String, acl: Acl) = getFile(id).writeText(acl.toString())
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() {
......
......@@ -20,11 +20,15 @@
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import android.os.Build
import android.os.IBinder
import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat
import android.text.TextUtils
import android.util.Base64
import android.util.Log
......@@ -66,6 +70,8 @@ object BaseService {
const val STOPPING = 3
const val STOPPED = 4
const val CONFIG_FILE = "shadowsocks.conf"
class Data internal constructor(private val service: Interface) {
@Volatile var profile: Profile? = null
@Volatile var state = STOPPED
......@@ -163,7 +169,8 @@ object BaseService {
}
}
internal fun buildShadowsocksConfig() {
internal var shadowsocksConfigFile: File? = null
internal fun buildShadowsocksConfig(): File {
val profile = profile!!
val config = JSONObject()
.put("server", profile.host)
......@@ -178,7 +185,13 @@ object BaseService {
.put("plugin", Commandline.toString(service.buildAdditionalArguments(pluginCmd)))
.put("plugin_opts", plugin.toString())
}
File(app.filesDir, "shadowsocks.json").bufferedWriter().use { it.write(config.toString()) }
// sensitive Shadowsocks config is stored in
val file = File((if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
app.deviceContext.noBackupFilesDir // only API 24+ will be in locked state
}), CONFIG_FILE)
shadowsocksConfigFile = file
file.writeText(config.toString())
return file
}
val aclFile: File? get() {
......@@ -228,14 +241,13 @@ object BaseService {
fun startNativeProcesses() {
val data = data
data.buildShadowsocksConfig()
val cmd = buildAdditionalArguments(arrayListOf(
File((this as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath,
"-u",
"-b", "127.0.0.1",
"-l", DataStore.portProxy.toString(),
"-t", "600",
"-c", "shadowsocks.json"))
"-c", data.buildShadowsocksConfig().absolutePath))
val acl = data.aclFile
if (acl != null) {
......@@ -278,6 +290,9 @@ object BaseService {
data.closeReceiverRegistered = false
}
data.shadowsocksConfigFile?.delete() // remove old config possibly in device storage
data.shadowsocksConfigFile = null
data.notification?.destroy()
data.notification = null
......
......@@ -37,8 +37,7 @@ object Executable {
fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
val exe = File(File(process, "cmdline").bufferedReader().use { it.readText() }
.split(Character.MIN_VALUE, limit = 2).first())
val exe = File(File(process, "cmdline").readText().split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
......
......@@ -62,7 +62,7 @@ class GuardedProcess(private val cmd: List<String>) {
process = ProcessBuilder(cmd)
.redirectErrorStream(true)
.directory(app.filesDir)
.directory(app.deviceContext.filesDir)
.start()
streamLogger(process.inputStream, Log::i)
......
......@@ -99,7 +99,7 @@ object LocalDnsService {
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true)
}
File(filesDir, file).bufferedWriter().use { it.write(config.toString()) }
File(app.deviceContext.filesDir, file).writeText(config.toString())
return file
}
......
......@@ -29,7 +29,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
override val socketFile = File(app.filesDir, "/stat_path")
override val socketFile = File(app.deviceContext.filesDir, "stat_path")
override fun accept(socket: LocalSocket) {
try {
......
......@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore
import java.io.File
......@@ -49,12 +50,12 @@ class TransproxyService : Service(), LocalDnsService.Interface {
"-u",
"-l", DataStore.portLocalDns.toString(), // ss-tunnel listens on the same port as overture
"-L", data.profile!!.remoteDns.split(",").first().trim() + ":53",
"-c", "shadowsocks.json") // config is already built by BaseService.Interface
"-c", data.shadowsocksConfigFile!!.absolutePath) // config is already built by BaseService.Interface
sstunnelProcess = GuardedProcess(cmd).start()
}
private fun startRedsocksDaemon() {
val config = """base {
File(app.deviceContext.filesDir, "redsocks.conf").writeText("""base {
log_debug = off;
log_info = off;
log = stderr;
......@@ -68,8 +69,7 @@ redsocks {
port = ${DataStore.portProxy};
type = socks5;
}
"""
File(filesDir, "redsocks.conf").bufferedWriter().use { it.write(config) }
""")
redsocksProcess = GuardedProcess(arrayListOf(
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath,
"-c", "redsocks.conf")
......
......@@ -52,7 +52,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") {
override val socketFile: File = File(filesDir, "protect_path")
override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
override fun accept(socket: LocalSocket) {
try {
......@@ -197,7 +197,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
var tries = 1
while (tries < 5) {
Thread.sleep(1000L * tries)
if (JniHelper.sendFd(fd, File(filesDir, "sock_path").absolutePath) != -1) return true
if (JniHelper.sendFd(fd, File(app.deviceContext.filesDir, "sock_path").absolutePath) != -1) return true
tries += 1
}
}
......
......@@ -31,7 +31,7 @@ import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
object DBHelper : OrmLiteSqliteOpenHelper(app, Key.PROFILE, null, 24) {
object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) {
@Suppress("UNCHECKED_CAST")
val profileDao: Dao<Profile, Int> by lazy { getDao(Profile::class.java) as Dao<Profile, Int> }
@Suppress("UNCHECKED_CAST")
......@@ -134,8 +134,14 @@ object DBHelper : OrmLiteSqliteOpenHelper(app, Key.PROFILE, null, 24) {
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
profileDao.executeRawNoArgs("COMMIT;")
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
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) {
kvPairDao.queryBuilder().where().`in`("key",
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
.forEach { PublicDatabase.kvPairDao.createOrUpdate(it) }
}
} catch (ex: Exception) {
app.track(ex)
......
......@@ -29,10 +29,11 @@ import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parsePort
import com.j256.ormlite.field.DataType
import com.j256.ormlite.field.DatabaseField
import java.io.Serializable
import java.net.URI
import java.util.*
class Profile {
class Profile : Serializable {
companion object {
private const val TAG = "ShadowParser"
private val pattern = """(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\[\]]*[-a-zA-Z0-9+&@#/%=~_|\[\]]""".toRegex()
......@@ -153,34 +154,34 @@ class Profile {
override fun toString() = toUri().toString()
fun serialize() {
DataStore.putString(Key.name, name)
DataStore.putString(Key.host, host)
DataStore.putString(Key.remotePort, remotePort.toString())
DataStore.putString(Key.password, password)
DataStore.putString(Key.route, route)
DataStore.putString(Key.remoteDns, remoteDns)
DataStore.putString(Key.method, method)
DataStore.privateStore.putString(Key.name, name)
DataStore.privateStore.putString(Key.host, host)
DataStore.privateStore.putString(Key.remotePort, remotePort.toString())
DataStore.privateStore.putString(Key.password, password)
DataStore.privateStore.putString(Key.route, route)
DataStore.privateStore.putString(Key.remoteDns, remoteDns)
DataStore.privateStore.putString(Key.method, method)
DataStore.proxyApps = proxyApps
DataStore.bypass = bypass
DataStore.putBoolean(Key.udpdns, udpdns)
DataStore.putBoolean(Key.ipv6, ipv6)
DataStore.privateStore.putBoolean(Key.udpdns, udpdns)
DataStore.privateStore.putBoolean(Key.ipv6, ipv6)
DataStore.individual = individual
DataStore.plugin = plugin ?: ""
DataStore.remove(Key.dirty)
DataStore.privateStore.remove(Key.dirty)
}
fun deserialize() {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = DataStore.getString(Key.name) ?: ""
host = DataStore.getString(Key.host) ?: ""
remotePort = parsePort(DataStore.getString(Key.remotePort), 8388, 1)
password = DataStore.getString(Key.password) ?: ""
method = DataStore.getString(Key.method) ?: ""
route = DataStore.getString(Key.route) ?: ""
remoteDns = DataStore.getString(Key.remoteDns) ?: ""
name = DataStore.privateStore.getString(Key.name) ?: ""
host = DataStore.privateStore.getString(Key.host) ?: ""
remotePort = parsePort(DataStore.privateStore.getString(Key.remotePort), 8388, 1)
password = DataStore.privateStore.getString(Key.password) ?: ""
method = DataStore.privateStore.getString(Key.method) ?: ""
route = DataStore.privateStore.getString(Key.route) ?: ""
remoteDns = DataStore.privateStore.getString(Key.remoteDns) ?: ""
proxyApps = DataStore.proxyApps
bypass = DataStore.bypass
udpdns = DataStore.getBoolean(Key.udpdns, false)
ipv6 = DataStore.getBoolean(Key.ipv6, false)
udpdns = DataStore.privateStore.getBoolean(Key.udpdns, false)
ipv6 = DataStore.privateStore.getBoolean(Key.ipv6, false)
individual = DataStore.individual
plugin = DataStore.plugin
}
......
......@@ -23,6 +23,8 @@ package com.github.shadowsocks.database
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
object ProfileManager {
private const val TAG = "ProfileManager"
......@@ -41,10 +43,10 @@ object ProfileManager {
profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns
}
val last = DBHelper.profileDao.queryRaw(DBHelper.profileDao.queryBuilder().selectRaw("MAX(userOrder)")
.prepareStatementString()).firstResult
val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
DBHelper.profileDao.createOrUpdate(profile)
PrivateDatabase.profileDao.createOrUpdate(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile)
} catch (ex: Exception) {
Log.e(TAG, "addProfile", ex)
......@@ -53,8 +55,11 @@ object ProfileManager {
return profile
}
/**
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/
fun updateProfile(profile: Profile): Boolean = try {
DBHelper.profileDao.update(profile)
PrivateDatabase.profileDao.update(profile)
true
} catch (ex: Exception) {
Log.e(TAG, "updateProfile", ex)
......@@ -63,7 +68,7 @@ object ProfileManager {
}
fun getProfile(id: Int): Profile? = try {
DBHelper.profileDao.queryForId(id)
PrivateDatabase.profileDao.queryForId(id)
} catch (ex: Exception) {
Log.e(TAG, "getProfile", ex)
app.track(ex)
......@@ -71,8 +76,9 @@ object ProfileManager {
}
fun delProfile(id: Int): Boolean = try {
DBHelper.profileDao.deleteById(id)
PrivateDatabase.profileDao.deleteById(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
true
} catch (ex: Exception) {
Log.e(TAG, "delProfile", ex)
......@@ -81,7 +87,7 @@ object ProfileManager {
}
fun getFirstProfile(): Profile? = try {
val result = DBHelper.profileDao.query(DBHelper.profileDao.queryBuilder().limit(1L).prepare())
val result = PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare())
if (result.size == 1) result[0] else null
} catch (ex: Exception) {
Log.e(TAG, "getAllProfiles", ex)
......@@ -90,7 +96,7 @@ object ProfileManager {
}
fun getAllProfiles(): List<Profile>? = try {
DBHelper.profileDao.query(DBHelper.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
} catch (ex: Exception) {
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
......
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteDatabase
import com.github.shadowsocks.App.Companion.app
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, 1) {
@Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
}
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) { }
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)
}
}
......@@ -157,7 +157,7 @@ object PluginManager {
var initialized = false
fun entryNotFound(): Nothing = throw IndexOutOfBoundsException("Plugin entry binary not found")
val list = ArrayList<String>()
val pluginDir = File(app.filesDir, "plugin")
val pluginDir = File(app.deviceContext.filesDir, "plugin")
(cr.query(uri, arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), null, null, null)
?: return null).use { cursor ->
if (!cursor.moveToFirst()) entryNotFound()
......
......@@ -21,115 +21,72 @@
package com.github.shadowsocks.preference
import android.os.Binder
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.DBHelper
import com.github.shadowsocks.database.KeyValuePair
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parsePort
import java.util.*
@Suppress("MemberVisibilityCanPrivate", "unused")
object DataStore : PreferenceDataStore() {
fun getBoolean(key: String?) = DBHelper.kvPairDao.queryForId(key)?.boolean
fun getFloat(key: String?) = DBHelper.kvPairDao.queryForId(key)?.float
fun getInt(key: String?) = DBHelper.kvPairDao.queryForId(key)?.int
fun getLong(key: String?) = DBHelper.kvPairDao.queryForId(key)?.long
fun getString(key: String?) = DBHelper.kvPairDao.queryForId(key)?.string
fun getStringSet(key: String?) = DBHelper.kvPairDao.queryForId(key)?.stringSet
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(key) ?: defValue
fun putBoolean(key: String?, value: Boolean?) = if (value == null) remove(key) else putBoolean(key, value)
fun putFloat(key: String?, value: Float?) = if (value == null) remove(key) else putFloat(key, value)
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
override fun putBoolean(key: String?, value: Boolean) {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putFloat(key: String?, value: Float) {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putInt(key: String?, value: Int) {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putLong(key: String?, value: Long) {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
DBHelper.kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
fireChangeListener(key)
}
fun remove(key: String?) {
DBHelper.kvPairDao.deleteById(key)
fireChangeListener(key)
}
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
object DataStore {
val publicStore = OrmLitePreferenceDataStore(PublicDatabase.kvPairDao)
// privateStore will only be used as temp storage for ProfileConfigFragment
val privateStore = OrmLitePreferenceDataStore(PrivateDatabase.kvPairDao)
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() }
private fun getLocalPort(key: String, default: Int): Int {
val value = getInt(key)
val value = publicStore.getInt(key)
return if (value != null) {
putString(key, value.toString())
publicStore.putString(key, value.toString())
value
} else parsePort(getString(key), default + userIndex)
} else parsePort(publicStore.getString(key), default + userIndex)
}
var profileId: Int
get() = getInt(Key.id) ?: 0
set(value) = putInt(Key.id, value)
get() = publicStore.getInt(Key.id) ?: 0
set(value) {
publicStore.putInt(Key.id, value)
if (DataStore.directBootAware) DirectBoot.update()
}
/**
* Setter is defined in MainActivity.onPreferenceDataStoreChanged.
*/
val directBootAware: Boolean get() = BootReceiver.enabled && (publicStore.getBoolean(Key.directBootAware) ?: false)
var serviceMode: String
get() = getString(Key.serviceMode) ?: Key.modeVpn
set(value) = putString(Key.serviceMode, value)
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
set(value) = publicStore.putString(Key.serviceMode, value)
var portProxy: Int
get() = getLocalPort(Key.portProxy, 1080)
set(value) = putString(Key.portProxy, value.toString())
set(value) = publicStore.putString(Key.portProxy, value.toString())
var portLocalDns: Int
get() = getLocalPort(Key.portLocalDns, 5450)
set(value) = putString(Key.portLocalDns, value.toString())
set(value) = publicStore.putString(Key.portLocalDns, value.toString())
var portTransproxy: Int
get() = getLocalPort(Key.portTransproxy, 8200)
set(value) = putString(Key.portTransproxy, value.toString())
set(value) = publicStore.putString(Key.portTransproxy, value.toString())
fun initGlobal() {
// temporary workaround for support lib bug
if (publicStore.getString(Key.serviceMode) == null) serviceMode = serviceMode
if (publicStore.getString(Key.portProxy) == null) portProxy = portProxy
if (publicStore.getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
if (publicStore.getString(Key.portTransproxy) == null) portTransproxy = portTransproxy
}
var proxyApps: Boolean
get() = getBoolean(Key.proxyApps) ?: false
set(value) = putBoolean(Key.proxyApps, value)
get() = privateStore.getBoolean(Key.proxyApps) ?: false
set(value) = privateStore.putBoolean(Key.proxyApps, value)
var bypass: Boolean
get() = getBoolean(Key.bypass) ?: false
set(value) = putBoolean(Key.bypass, value)
get() = privateStore.getBoolean(Key.bypass) ?: false
set(value) = privateStore.putBoolean(Key.bypass, value)
var individual: String
get() = getString(Key.individual) ?: ""
set(value) = putString(Key.individual, value)
get() = privateStore.getString(Key.individual) ?: ""
set(value) = privateStore.putString(Key.individual, value)
var plugin: String
get() = getString(Key.plugin) ?: ""
set(value) = putString(Key.plugin, value)
get() = privateStore.getString(Key.plugin) ?: ""
set(value) = privateStore.putString(Key.plugin, value)
var dirty: Boolean
get() = getBoolean(Key.dirty) ?: false
set(value) = putBoolean(Key.dirty, value)
fun initGlobal() {
// temporary workaround for support lib bug
if (getString(Key.serviceMode) == null) serviceMode = serviceMode
if (getString(Key.portProxy) == null) portProxy = portProxy
if (getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
if (getString(Key.portTransproxy) == null) portTransproxy = portTransproxy
}
get() = privateStore.getBoolean(Key.dirty) ?: false
set(value) = privateStore.putBoolean(Key.dirty, value)
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.KeyValuePair
import com.j256.ormlite.dao.Dao
import java.util.HashSet
@Suppress("MemberVisibilityCanPrivate", "unused")
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() {
fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean
fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float
fun getInt(key: String?) = kvPairDao.queryForId(key)?.int
fun getLong(key: String?) = kvPairDao.queryForId(key)?.long
fun getString(key: String?) = kvPairDao.queryForId(key)?.string
fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
override fun getInt(key: String?, defValue: Int) = getInt(key) ?: defValue
override fun getLong(key: String?, defValue: Long) = getLong(key) ?: defValue
override fun getString(key: String?, defValue: String?) = getString(key) ?: defValue
override fun getStringSet(key: String?, defValue: MutableSet<String>?) = getStringSet(key) ?: defValue
fun putBoolean(key: String?, value: Boolean?) = if (value == null) remove(key) else putBoolean(key, value)
fun putFloat(key: String?, value: Float?) = if (value == null) remove(key) else putFloat(key, value)
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
override fun putBoolean(key: String?, value: Boolean) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putFloat(key: String?, value: Float) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putInt(key: String?, value: Int) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putLong(key: String?, value: Long) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
fireChangeListener(key)
}
fun remove(key: String?) {
kvPairDao.deleteById(key)
fireChangeListener(key)
}
private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()
private fun fireChangeListener(key: String?) = listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }
fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.add(listener)
fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) = listeners.remove(listener)
}
......@@ -21,7 +21,11 @@
package com.github.shadowsocks.utils
object Key {
const val PROFILE = "profile.db"
/**
* Public config that doesn't need to be kept secret.
*/
const val DB_PUBLIC = "config.db"
const val DB_PROFILE = "profile.db"
const val id = "profileId"
const val name = "profileName"
......@@ -39,6 +43,7 @@ object Key {
const val route = "route"
const val isAutoConnect = "isAutoConnect"
const val directBootAware = "directBootAware"
const val proxyApps = "isProxyApps"
const val bypass = "isBypassApps"
......
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.utils
import android.annotation.TargetApi
import android.content.Context
import android.content.ContextWrapper
@TargetApi(24)
class DeviceContext(context: Context) : ContextWrapper(context.createDeviceProtectedStorageContext()) {
/**
* Thou shalt not get the REAL underlying application context which would no longer be operating under device
* protected storage.
*/
override fun getApplicationContext(): Context = this
}
package com.github.shadowsocks.utils
import android.annotation.TargetApi
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import java.io.File
import java.io.FileNotFoundException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
@TargetApi(24)
object DirectBoot {
private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile")
fun getDeviceProfile(): Profile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
} catch (_: FileNotFoundException) { null }
fun clean() {
file.delete()
File(app.deviceContext.noBackupFilesDir, BaseService.CONFIG_FILE).delete()
}
fun update() {
val profile = ProfileManager.getProfile(DataStore.profileId) // app.currentProfile will call this
if (profile == null) clean() else ObjectOutputStream(file.outputStream()).use { it.writeObject(profile) }
}
}
......@@ -42,6 +42,7 @@ object TcpFastOpen {
val sendEnabled: Boolean get() {
val file = File("/proc/sys/net/ipv4/tcp_fastopen")
// File.readText doesn't work since this special file will return length 0
return file.canRead() && file.bufferedReader().use { it.readText() }.trim().toInt() and 1 > 0
}
......
......@@ -57,6 +57,9 @@
<string name="auto_connect_summary">Enable Shadowsocks on startup</string>
<string name="auto_connect_summary_v24">Enable Shadowsocks on startup. Recommended to use always-on VPN
instead</string>
<string name="direct_boot_aware">Direct Boot Aware</string>
<string name="direct_boot_aware_summary">Allow Shadowsocks to start before your device is unlocked (your selected
profile information will be less protected)</string>
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="udp_dns">DNS Forwarding</string>
......
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<full-backup-content xmlns:tools="http://schemas.android.com/tools"
tools:ignore="FullBackupContent">
<include domain="database" path="profile.db"/>
<!-- No device storage yet in Android 6.0 -->
<include domain="database" path="config.db"/>
<include domain="file" path="custom-rules.acl"/>
<include domain="device_database" path="config.db"/>
<include domain="device_file" path="custom-rules.acl"/>
</full-backup-content>
......@@ -5,6 +5,11 @@
android:persistent="false"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
<!-- Direct Boot Aware alone doesn't do anything without auto connect -->
<SwitchPreference android:key="directBootAware"
android:dependency="isAutoConnect"
android:summary="@string/direct_boot_aware_summary"
android:title="@string/direct_boot_aware"/>
<SwitchPreference android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/>
......
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