11package tech.httptoolkit.android
22
33import android.app.Application
4- import android.content.Context
4+ import android.content.*
5+ import android.os.Build
56import android.util.Log
67import com.android.installreferrer.api.InstallReferrerClient
78import com.android.installreferrer.api.InstallReferrerClient.InstallReferrerResponse
@@ -12,7 +13,8 @@ import com.google.android.gms.analytics.HitBuilders
1213import com.google.android.gms.analytics.Tracker
1314import io.sentry.Sentry
1415import io.sentry.android.AndroidSentryClientFactory
15- import kotlinx.coroutines.*
16+ import kotlinx.coroutines.Dispatchers
17+ import kotlinx.coroutines.withContext
1618import net.swiftzer.semver.SemVer
1719import okhttp3.OkHttpClient
1820import okhttp3.Request
@@ -22,14 +24,51 @@ import java.util.concurrent.atomic.AtomicBoolean
2224import kotlin.coroutines.resume
2325import kotlin.coroutines.suspendCoroutine
2426
27+ private const val VPN_START_TIME_PREF = " vpn-start-time"
28+ private const val APP_CRASHED_PREF = " app-crashed"
29+ private const val FIRST_RUN_PREF = " is-first-run"
30+
31+ private val isProbablyEmulator =
32+ Build .FINGERPRINT .startsWith(" generic" )
33+ || Build .FINGERPRINT .startsWith(" unknown" )
34+ || Build .MODEL .contains(" google_sdk" )
35+ || Build .MODEL .contains(" Emulator" )
36+ || Build .MODEL .contains(" Android SDK built for x86" )
37+ || Build .BOARD == " QC_Reference_Phone"
38+ || Build .MANUFACTURER .contains(" Genymotion" )
39+ || Build .HOST .startsWith(" Build" )
40+ || (Build .BRAND .startsWith(" generic" ) && Build .DEVICE .startsWith(" generic" ))
41+ || Build .PRODUCT == " google_sdk"
42+
43+ private val bootTime = (System .currentTimeMillis() - android.os.SystemClock .elapsedRealtime())
2544
2645class HttpToolkitApplication : Application () {
2746
2847 private var analytics: GoogleAnalytics ? = null
2948 private var ga: Tracker ? = null
3049
50+ private lateinit var prefs: SharedPreferences
51+ private var vpnWasKilled: Boolean = false
52+
53+ var vpnShouldBeRunning: Boolean
54+ get() {
55+ return prefs.getLong(VPN_START_TIME_PREF , - 1 ) > bootTime
56+ }
57+ set(value) {
58+ if (value) {
59+ prefs.edit().putLong(VPN_START_TIME_PREF , System .currentTimeMillis()).apply ()
60+ } else {
61+ prefs.edit().putLong(VPN_START_TIME_PREF , - 1 ).apply ()
62+ }
63+ }
64+
3165 override fun onCreate () {
3266 super .onCreate()
67+ prefs = getSharedPreferences(" tech.httptoolkit.android" , MODE_PRIVATE )
68+
69+ Thread .setDefaultUncaughtExceptionHandler { _, _ ->
70+ prefs.edit().putBoolean(APP_CRASHED_PREF , true ).apply ()
71+ }
3372
3473 if (BuildConfig .SENTRY_DSN != null ) {
3574 Sentry .init (BuildConfig .SENTRY_DSN , AndroidSentryClientFactory (this ))
@@ -41,18 +80,35 @@ class HttpToolkitApplication : Application() {
4180 resumeEvents() // Resume events on app startup, in case they were paused and we crashed
4281 }
4382
83+ // Check if we've been recreated unexpectedly, with no crashes in the meantime:
84+ val appCrashed = prefs.getBoolean(APP_CRASHED_PREF , false )
85+ prefs.edit().putBoolean(APP_CRASHED_PREF , false ).apply ()
86+
87+ vpnWasKilled = vpnShouldBeRunning && ! isVpnActive() && ! appCrashed && ! isProbablyEmulator
88+ if (vpnWasKilled) {
89+ Sentry .capture(" VPN killed in the background" )
90+ // The UI will show an alert next time the MainActivity is created.
91+ }
92+
4493 Log .i(TAG , " App created" )
4594 }
4695
96+ /* *
97+ * Check whether the VPN was killed as a sleeping background process, and then
98+ * reset that state so that future checks (until it's next killed) return false
99+ */
100+ fun popVpnKilledState (): Boolean {
101+ return vpnWasKilled
102+ .also { this .vpnWasKilled = false }
103+ }
104+
47105 /* *
48106 * Grab any first run params, drop them for future usage, and return them.
49107 * This will return first-run params at most once (per install).
50108 */
51109 suspend fun popFirstRunParams (): String? {
52- val prefs = getSharedPreferences(" tech.httptoolkit.android" , MODE_PRIVATE )
53-
54- val isFirstRun = prefs.getBoolean(" is-first-run" , true )
55- prefs.edit().putBoolean(" is-first-run" , false ).apply ()
110+ val isFirstRun = prefs.getBoolean(FIRST_RUN_PREF , true )
111+ prefs.edit().putBoolean(FIRST_RUN_PREF , false ).apply ()
56112
57113 val installTime = packageManager.getPackageInfo(packageName, 0 ).firstInstallTime
58114 val now = System .currentTimeMillis()
@@ -96,7 +152,6 @@ class HttpToolkitApplication : Application() {
96152 }
97153 })
98154 }
99-
100155 }
101156
102157 var lastProxy: ProxyConfig ?
0 commit comments