Commit a7d77461 authored by Mygod's avatar Mygod

Fix traffic stats not saved in direct boot mode

parent ecd3cb54
...@@ -23,6 +23,7 @@ package com.github.shadowsocks ...@@ -23,6 +23,7 @@ package com.github.shadowsocks
import android.app.Application import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
...@@ -33,8 +34,8 @@ import android.content.res.Configuration ...@@ -33,8 +34,8 @@ import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.UserManager
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
...@@ -73,6 +74,10 @@ class App : Application() { ...@@ -73,6 +74,10 @@ class App : Application() {
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) } private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
private val exceptionParser by lazy { StandardExceptionParser(this, null) } private val exceptionParser by lazy { StandardExceptionParser(this, null) }
val info: PackageInfo by lazy { getPackageInfo(packageName) } val info: PackageInfo by lazy { getPackageInfo(packageName) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
}
fun getPackageInfo(packageName: String) = fun getPackageInfo(packageName: String) =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!! packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!!
...@@ -139,8 +144,9 @@ class App : Application() { ...@@ -139,8 +144,9 @@ class App : Application() {
app.track(e) app.track(e)
} }
// handle data restored // handle data restored/crash
if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update() if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&
(getSystemService(Context.USER_SERVICE) as UserManager).isUserUnlocked) DirectBoot.flushTrafficStats()
TcpFastOpen.enabledAsync(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabledAsync(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) { if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets val assetManager = assets
......
...@@ -20,12 +20,12 @@ ...@@ -20,12 +20,12 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.admin.DevicePolicyManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
...@@ -39,20 +39,17 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() { ...@@ -39,20 +39,17 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
DataStore.initGlobal() DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
val boot = findPreference(Key.isAutoConnect) as SwitchPreference val boot = findPreference(Key.isAutoConnect) as SwitchPreference
val directBootAwareListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true
}
boot.setOnPreferenceChangeListener { _, value -> boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean BootReceiver.enabled = value as Boolean
directBootAwareListener.onPreferenceChange(null, DataStore.directBootAware) true
} }
boot.isChecked = BootReceiver.enabled boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware) val canToggleLocked = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && requireContext().getSystemService(DevicePolicyManager::class.java) if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) if (app.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba) true
} else canToggleLocked.parent!!.removePreference(canToggleLocked)
val tfo = findPreference(Key.tfo) as SwitchPreference val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled tfo.isChecked = TcpFastOpen.sendEnabled
......
...@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient ...@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File
import java.io.IOException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.security.MessageDigest import java.security.MessageDigest
import java.util.* import java.util.*
...@@ -145,22 +146,32 @@ object BaseService { ...@@ -145,22 +146,32 @@ object BaseService {
} }
internal fun updateTrafficTotal(tx: Long, rx: Long) { internal fun updateTrafficTotal(tx: Long, rx: Long) {
// this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition) try {
val profile = ProfileManager.getProfile((profile ?: return).id) ?: return // this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
profile.tx += tx val profile = ProfileManager.getProfile((profile ?: return).id) ?: return
profile.rx += rx profile.tx += tx
ProfileManager.updateProfile(profile) profile.rx += rx
app.handler.post { ProfileManager.updateProfile(profile)
if (bandwidthListeners.isNotEmpty()) { app.handler.post {
val n = callbacks.beginBroadcast() if (bandwidthListeners.isNotEmpty()) {
for (i in 0 until n) { val n = callbacks.beginBroadcast()
try { for (i in 0 until n) {
val item = callbacks.getBroadcastItem(i) try {
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id) val item = callbacks.getBroadcastItem(i)
} catch (_: Exception) { } // ignore if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id)
} catch (_: Exception) { } // ignore
}
callbacks.finishBroadcast()
} }
callbacks.finishBroadcast()
} }
} catch (e: IOException) {
if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot
val profile = DirectBoot.getDeviceProfile()!!
profile.tx += tx
profile.rx += rx
profile.dirty = true
DirectBoot.update(profile)
DirectBoot.listenForUnlock()
} }
} }
......
...@@ -82,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface { ...@@ -82,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface {
} }
override fun onClick() { override fun onClick() {
if (isLocked && !DataStore.directBootAware) unlockAndRun(this::toggle) else toggle() if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle()
} }
private fun toggle() { private fun toggle() {
......
...@@ -147,6 +147,9 @@ class Profile : Serializable { ...@@ -147,6 +147,9 @@ class Profile : Serializable {
@DatabaseField @DatabaseField
var plugin: String? = null var plugin: String? = null
// not persisted in db, only used by direct boot
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)
val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!! val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!!
......
...@@ -20,11 +20,13 @@ ...@@ -20,11 +20,13 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import java.io.IOException
import java.sql.SQLException import java.sql.SQLException
/** /**
...@@ -62,9 +64,11 @@ object ProfileManager { ...@@ -62,9 +64,11 @@ object ProfileManager {
@Throws(SQLException::class) @Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile) fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
@Throws(IOException::class)
fun getProfile(id: Int): Profile? = try { fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryForId(id) PrivateDatabase.profileDao.queryForId(id)
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex) Log.e(TAG, "getProfile", ex)
app.track(ex) app.track(ex)
null null
...@@ -77,17 +81,21 @@ object ProfileManager { ...@@ -77,17 +81,21 @@ object ProfileManager {
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean() if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
} }
@Throws(IOException::class)
fun getFirstProfile(): Profile? = try { fun getFirstProfile(): Profile? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull() PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getFirstProfile", ex) Log.e(TAG, "getFirstProfile", ex)
app.track(ex) app.track(ex)
null null
} }
@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.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex) Log.e(TAG, "getAllProfiles", ex)
app.track(ex) app.track(ex)
null null
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
package com.github.shadowsocks.preference package com.github.shadowsocks.preference
import android.os.Binder import android.os.Binder
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BootReceiver import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.database.PrivateDatabase import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase import com.github.shadowsocks.database.PublicDatabase
...@@ -49,10 +50,8 @@ object DataStore { ...@@ -49,10 +50,8 @@ object DataStore {
publicStore.putInt(Key.id, value) publicStore.putInt(Key.id, value)
if (DataStore.directBootAware) DirectBoot.update() if (DataStore.directBootAware) DirectBoot.update()
} }
/** val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true
* Setter is defined in MainActivity.onPreferenceDataStoreChanged. val directBootAware: Boolean get() = app.directBootSupported && canToggleLocked
*/
val directBootAware: Boolean get() = BootReceiver.enabled && publicStore.getBoolean(Key.directBootAware) == true
var serviceMode: String var serviceMode: String
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
set(value) = publicStore.putString(Key.serviceMode, value) set(value) = publicStore.putString(Key.serviceMode, value)
......
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
...@@ -12,8 +16,9 @@ import java.io.ObjectInputStream ...@@ -12,8 +16,9 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
@TargetApi(24) @TargetApi(24)
object DirectBoot { object DirectBoot : BroadcastReceiver() {
private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile") private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile")
private var registered = false
fun getDeviceProfile(): Profile? = try { fun getDeviceProfile(): Profile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile } ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
...@@ -24,8 +29,26 @@ object DirectBoot { ...@@ -24,8 +29,26 @@ object DirectBoot {
File(app.deviceContext.noBackupFilesDir, BaseService.CONFIG_FILE).delete() File(app.deviceContext.noBackupFilesDir, BaseService.CONFIG_FILE).delete()
} }
fun update() { /**
val profile = ProfileManager.getProfile(DataStore.profileId) // app.currentProfile will call this * app.currentProfile will call this.
if (profile == null) clean() else ObjectOutputStream(file.outputStream()).use { it.writeObject(profile) } */
fun update(profile: Profile? = ProfileManager.getProfile(DataStore.profileId)) =
if (profile == null) clean() else ObjectOutputStream(file.outputStream()).use { it.writeObject(profile) }
fun flushTrafficStats() {
val profile = getDeviceProfile()
if (profile?.dirty == true) ProfileManager.updateProfile(profile)
update()
}
fun listenForUnlock() {
if (registered) return
app.registerReceiver(this, IntentFilter(Intent.ACTION_BOOT_COMPLETED))
registered = true
}
override fun onReceive(context: Context, intent: Intent) {
flushTrafficStats()
app.unregisterReceiver(this)
registered = false
} }
} }
...@@ -57,9 +57,8 @@ ...@@ -57,9 +57,8 @@
<string name="auto_connect_summary">Enable Shadowsocks on startup</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 <string name="auto_connect_summary_v24">Enable Shadowsocks on startup. Recommended to use always-on VPN
instead</string> instead</string>
<string name="direct_boot_aware">Direct Boot Aware</string> <string name="direct_boot_aware">Allow Toggling in Lock Screen</string>
<string name="direct_boot_aware_summary">Allow Shadowsocks to start before your device is unlocked (your selected <string name="direct_boot_aware_summary">Your selected profile information will be less protected</string>
profile information will be less protected)</string>
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</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="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="udp_dns">DNS Forwarding</string> <string name="udp_dns">DNS Forwarding</string>
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
android:title="@string/auto_connect"/> android:title="@string/auto_connect"/>
<!-- Direct Boot Aware alone doesn't do anything without auto connect --> <!-- Direct Boot Aware alone doesn't do anything without auto connect -->
<SwitchPreference android:key="directBootAware" <SwitchPreference android:key="directBootAware"
android:dependency="isAutoConnect"
android:summary="@string/direct_boot_aware_summary" android:summary="@string/direct_boot_aware_summary"
android:title="@string/direct_boot_aware"/> android:title="@string/direct_boot_aware"/>
<SwitchPreference android:key="tcp_fastopen" <SwitchPreference android:key="tcp_fastopen"
......
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