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
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
......@@ -33,8 +34,8 @@ import android.content.res.Configuration
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.UserManager
import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import android.widget.Toast
......@@ -73,6 +74,10 @@ class App : Application() {
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
private val exceptionParser by lazy { StandardExceptionParser(this, null) }
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) =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!!
......@@ -139,8 +144,9 @@ class App : Application() {
app.track(e)
}
// handle data restored
if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update()
// handle data restored/crash
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))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets
......
......@@ -20,12 +20,12 @@
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
......@@ -39,20 +39,17 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
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 ->
BootReceiver.enabled = value as Boolean
directBootAwareListener.onPreferenceChange(null, DataStore.directBootAware)
true
}
boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && requireContext().getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER)
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba)
val canToggleLocked = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
if (app.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true
} else canToggleLocked.parent!!.removePreference(canToggleLocked)
val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled
......
......@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.io.File
import java.io.IOException
import java.net.UnknownHostException
import java.security.MessageDigest
import java.util.*
......@@ -145,6 +146,7 @@ object BaseService {
}
internal fun updateTrafficTotal(tx: Long, rx: Long) {
try {
// this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
val profile = ProfileManager.getProfile((profile ?: return).id) ?: return
profile.tx += tx
......@@ -162,6 +164,15 @@ object BaseService {
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()
}
}
internal var shadowsocksConfigFile: File? = null
......
......@@ -82,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface {
}
override fun onClick() {
if (isLocked && !DataStore.directBootAware) unlockAndRun(this::toggle) else toggle()
if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle()
}
private fun toggle() {
......
......@@ -147,6 +147,9 @@ class Profile : Serializable {
@DatabaseField
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 formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!!
......
......@@ -20,11 +20,13 @@
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException
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
import java.io.IOException
import java.sql.SQLException
/**
......@@ -62,9 +64,11 @@ object ProfileManager {
@Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
@Throws(IOException::class)
fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryForId(id)
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex)
app.track(ex)
null
......@@ -77,17 +81,21 @@ object ProfileManager {
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
}
@Throws(IOException::class)
fun getFirstProfile(): Profile? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getFirstProfile", ex)
app.track(ex)
null
}
@Throws(IOException::class)
fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
null
......
......@@ -21,6 +21,7 @@
package com.github.shadowsocks.preference
import android.os.Binder
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase
......@@ -49,10 +50,8 @@ object DataStore {
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) == true
val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true
val directBootAware: Boolean get() = app.directBootSupported && canToggleLocked
var serviceMode: String
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
set(value) = publicStore.putString(Key.serviceMode, value)
......
package com.github.shadowsocks.utils
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.bg.BaseService
import com.github.shadowsocks.database.Profile
......@@ -12,8 +16,9 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream
@TargetApi(24)
object DirectBoot {
object DirectBoot : BroadcastReceiver() {
private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile")
private var registered = false
fun getDeviceProfile(): Profile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
......@@ -24,8 +29,26 @@ object DirectBoot {
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.
*/
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 @@
<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="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 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>
......
......@@ -7,7 +7,6 @@
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"
......
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