Unverified Commit f9ff9f66 authored by Mygod's avatar Mygod Committed by GitHub

Merge pull request #2196 from shadowsocks/autoconnect+

Refine auto connecting
parents e7b672f9 f6aa874c
......@@ -80,6 +80,7 @@
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
......
......@@ -25,6 +25,9 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.preference.DataStore
......@@ -40,11 +43,16 @@ class BootReceiver : BroadcastReceiver() {
}
override fun onReceive(context: Context, intent: Intent) {
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.persistAcrossReboot) { // sanity check
enabled = false
return
}
if (DataStore.directBootAware == locked) Core.startService()
val doStart = when (intent.action) {
Intent.ACTION_BOOT_COMPLETED -> !DataStore.directBootAware
Intent.ACTION_LOCKED_BOOT_COMPLETED -> DataStore.directBootAware
else -> DataStore.directBootAware ||
Build.VERSION.SDK_INT >= 24 && app.getSystemService<UserManager>()?.isUserUnlocked != false
}
if (doStart) Core.startService()
}
}
......@@ -29,6 +29,7 @@ import android.util.Log
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.aidl.IShadowsocksService
......@@ -36,6 +37,7 @@ import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.core.R
import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver
import com.github.shadowsocks.utils.printLog
......@@ -76,6 +78,7 @@ object BaseService {
var notification: ServiceNotification? = null
val closeReceiver = broadcastReceiver { _, intent ->
when (intent.action) {
Intent.ACTION_SHUTDOWN -> service.persistStats()
Action.RELOAD -> service.forceLoad()
else -> service.stopRunner()
}
......@@ -284,10 +287,16 @@ object BaseService {
data.changeState(State.Stopped, msg)
// stop the service if nothing has bound to it
if (restart) startRunner() else stopSelf()
if (restart) startRunner() else {
BootReceiver.enabled = false
stopSelf()
}
}
}
fun persistStats() =
listOfNotNull(data.proxy, data.udpFallback).forEach { it.trafficMonitor?.persistStats(it.profile.id) }
suspend fun preInit() { }
suspend fun resolver(host: String) = InetAddress.getAllByName(host)
suspend fun openConnection(url: URL) = url.openConnection()
......@@ -309,6 +318,7 @@ object BaseService {
data.proxy = proxy
data.udpFallback = if (fallback == null) null else ProxyInstance(fallback, profile.route)
BootReceiver.enabled = DataStore.persistAcrossReboot
if (!data.closeReceiverRegistered) {
registerReceiver(data.closeReceiver, IntentFilter().apply {
addAction(Action.RELOAD)
......
......@@ -28,14 +28,14 @@ import com.github.shadowsocks.Core
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.AclSyncer
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.plugin.PluginManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.*
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.signaturesCompat
import com.github.shadowsocks.utils.useCancellable
import kotlinx.coroutines.*
import java.io.File
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.net.UnknownHostException
......@@ -138,22 +138,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
fun shutdown(scope: CoroutineScope) {
trafficMonitor?.apply {
thread.shutdown(scope)
// Make sure update total traffic when stopping the runner
try {
// profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
val profile = ProfileManager.getProfile(profile.id) ?: return
profile.tx += current.txTotal
profile.rx += current.rxTotal
ProfileManager.updateProfile(profile)
} catch (e: IOException) {
if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot
val profile = DirectBoot.getDeviceProfile()!!.toList().filterNotNull().single { it.id == profile.id }
profile.tx += current.txTotal
profile.rx += current.rxTotal
profile.dirty = true
DirectBoot.update(profile)
DirectBoot.listenForUnlock()
}
persistStats(profile.id) // Make sure update total traffic when stopping the runner
}
trafficMonitor = null
configFile?.delete() // remove old config possibly in device storage
......
......@@ -23,7 +23,10 @@ package com.github.shadowsocks.bg
import android.net.LocalSocket
import android.os.SystemClock
import com.github.shadowsocks.aidl.TrafficStats
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.LocalSocketListener
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
......@@ -52,6 +55,7 @@ class TrafficMonitor(statFile: File) {
var out = TrafficStats()
private var timestampLast = 0L
private var dirty = false
private var persisted = false
fun requestUpdate(): Pair<TrafficStats, Boolean> {
val now = SystemClock.elapsedRealtime()
......@@ -79,4 +83,24 @@ class TrafficMonitor(statFile: File) {
}
return Pair(out, updated)
}
fun persistStats(id: Long) {
check(!persisted) { "Double persisting?" }
persisted = true
try {
// profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
val profile = ProfileManager.getProfile(id) ?: return
profile.tx += current.txTotal
profile.rx += current.rxTotal
ProfileManager.updateProfile(profile)
} catch (e: IOException) {
if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot
val profile = DirectBoot.getDeviceProfile()!!.toList().filterNotNull().single { it.id == id }
profile.tx += current.txTotal
profile.rx += current.rxTotal
profile.dirty = true
DirectBoot.update(profile)
DirectBoot.listenForUnlock()
}
}
}
......@@ -22,6 +22,7 @@ package com.github.shadowsocks.preference
import android.os.Binder
import androidx.preference.PreferenceDataStore
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase
......@@ -61,6 +62,8 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var profileId: Long
get() = publicStore.getLong(Key.id) ?: 0
set(value) = publicStore.putLong(Key.id, value)
val persistAcrossReboot get() = publicStore.getBoolean(Key.persistAcrossReboot)
?: BootReceiver.enabled.also { publicStore.putBoolean(Key.persistAcrossReboot, it) }
val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true
val directBootAware: Boolean get() = Core.directBootSupported && canToggleLocked
val tcpFastOpen: Boolean get() = TcpFastOpen.sendEnabled && DataStore.publicStore.getBoolean(Key.tfo, true)
......@@ -101,6 +104,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
* Initialize settings that have complicated default values.
*/
fun initGlobal() {
persistAcrossReboot
if (publicStore.getBoolean(Key.tfo) == null) publicStore.putBoolean(Key.tfo, tcpFastOpen)
if (publicStore.getString(Key.portProxy) == null) portProxy = portProxy
if (publicStore.getString(Key.portLocalDns) == null) portLocalDns = portLocalDns
......
......@@ -43,7 +43,7 @@ object Key {
const val route = "route"
const val isAutoConnect = "isAutoConnect"
const val persistAcrossReboot = "isAutoConnect"
const val directBootAware = "directBootAware"
const val proxyApps = "isProxyApps"
......
......@@ -53,9 +53,7 @@
<string name="bypass_apps">Bypass</string>
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
<string name="auto_connect">Auto Connect</string>
<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="auto_connect_summary">Enable Shadowsocks on startup/app update if it was running before</string>
<string name="direct_boot_aware">Allow Toggling in Lock Screen</string>
<string name="direct_boot_aware_summary">Your selected profile information will be less protected</string>
<string name="tcp_fastopen_summary">Toggling might require ROOT permission</string>
......
......@@ -50,13 +50,10 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
val boot = findPreference<SwitchPreference>(Key.isAutoConnect)!!
boot.setOnPreferenceChangeListener { _, value ->
findPreference<SwitchPreference>(Key.persistAcrossReboot)!!.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
}
boot.isChecked = BootReceiver.enabled
if (Build.VERSION.SDK_INT >= 24) boot.setSummary(R.string.auto_connect_summary_v24)
val canToggleLocked = findPreference<Preference>(Key.directBootAware)!!
if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
......
......@@ -3,7 +3,6 @@
app:initialExpandedChildrenCount="4">
<SwitchPreference
app:key="isAutoConnect"
app:persistent="false"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect"/>
<SwitchPreference
......
......@@ -157,12 +157,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
stats = findPreference(Key.controlStats)!!
controlImport = findPreference(Key.controlImport)!!
findPreference<SwitchPreference>(Key.isAutoConnect)!!.apply {
setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
}
isChecked = BootReceiver.enabled
findPreference<SwitchPreference>(Key.persistAcrossReboot)!!.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
}
tfo = findPreference(Key.tfo)!!
......
......@@ -23,7 +23,6 @@
app:initialExpandedChildrenCount="3">
<SwitchPreference
app:key="isAutoConnect"
app:persistent="false"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect"/>
<SwitchPreference
......
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