11package tech.httptoolkit.android
22
33import android.app.Application
4- import android.content.*
4+ import android.content.Context
5+ import android.content.SharedPreferences
56import android.content.pm.PackageManager
67import android.os.Build
78import android.util.Log
9+ import androidx.core.content.edit
810import com.android.installreferrer.api.InstallReferrerClient
911import com.android.installreferrer.api.InstallReferrerClient.InstallReferrerResponse
1012import com.android.installreferrer.api.InstallReferrerStateListener
@@ -16,7 +18,9 @@ import net.swiftzer.semver.SemVer
1618import okhttp3.OkHttpClient
1719import okhttp3.Request
1820import java.text.SimpleDateFormat
19- import java.util.*
21+ import java.util.Calendar
22+ import java.util.Date
23+ import java.util.Locale
2024import java.util.concurrent.atomic.AtomicBoolean
2125import kotlin.coroutines.resume
2226import kotlin.coroutines.suspendCoroutine
@@ -25,19 +29,22 @@ private const val VPN_START_TIME_PREF = "vpn-start-time"
2529private const val LAST_UPDATE_CHECK_TIME_PREF = " update-check-time"
2630private const val APP_CRASHED_PREF = " app-crashed"
2731private const val FIRST_RUN_PREF = " is-first-run"
32+ private const val HTTP_TOOLKIT_PREFERENCES_NAME = " tech.httptoolkit.android"
33+ private const val LAST_PROXY_CONFIG_PREF_KEY = " last-proxy-config"
34+ private const val TIME_PATTERN = " yyyy-MM-dd'T'HH:mm:ss"
2835
2936private val isProbablyEmulator =
30- Build .FINGERPRINT .startsWith(" generic" )
31- || Build .FINGERPRINT .startsWith(" unknown" )
32- || Build .MODEL .contains(" google_sdk" )
33- || Build .MODEL .contains(" sdk_gphone" )
34- || Build .MODEL .contains(" Emulator" )
35- || Build .MODEL .contains(" Android SDK built for x86" )
36- || Build .BOARD == " QC_Reference_Phone"
37- || Build .MANUFACTURER .contains(" Genymotion" )
38- || Build .HOST .startsWith(" Build" )
39- || (Build .BRAND .startsWith(" generic" ) && Build .DEVICE .startsWith(" generic" ))
40- || Build .PRODUCT == " google_sdk"
37+ Build .FINGERPRINT .startsWith(" generic" )
38+ || Build .FINGERPRINT .startsWith(" unknown" )
39+ || Build .MODEL .contains(" google_sdk" )
40+ || Build .MODEL .contains(" sdk_gphone" )
41+ || Build .MODEL .contains(" Emulator" )
42+ || Build .MODEL .contains(" Android SDK built for x86" )
43+ || Build .BOARD == " QC_Reference_Phone"
44+ || Build .MANUFACTURER .contains(" Genymotion" )
45+ || Build .HOST .startsWith(" Build" )
46+ || (Build .BRAND .startsWith(" generic" ) && Build .DEVICE .startsWith(" generic" ))
47+ || Build .PRODUCT == " google_sdk"
4148
4249private val bootTime = (System .currentTimeMillis() - android.os.SystemClock .elapsedRealtime())
4350
@@ -52,23 +59,23 @@ class HttpToolkitApplication : Application() {
5259 }
5360 set(value) {
5461 if (value) {
55- prefs.edit(). putLong(VPN_START_TIME_PREF , System .currentTimeMillis()). apply ()
62+ prefs.edit { putLong(VPN_START_TIME_PREF , System .currentTimeMillis()) }
5663 } else {
57- prefs.edit(). putLong(VPN_START_TIME_PREF , - 1 ). apply ()
64+ prefs.edit { putLong(VPN_START_TIME_PREF , - 1 ) }
5865 }
5966 }
6067
6168 override fun onCreate () {
6269 super .onCreate()
63- prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
70+ prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
6471
6572 Thread .setDefaultUncaughtExceptionHandler { _, _ ->
66- prefs.edit(). putBoolean(APP_CRASHED_PREF , true ). apply ()
73+ prefs.edit { putBoolean(APP_CRASHED_PREF , true ) }
6774 }
6875
6976 // Check if we've been recreated unexpectedly, with no crashes in the meantime:
7077 val appCrashed = prefs.getBoolean(APP_CRASHED_PREF , false )
71- prefs.edit(). putBoolean(APP_CRASHED_PREF , false ). apply ()
78+ prefs.edit { putBoolean(APP_CRASHED_PREF , false ) }
7279
7380 vpnWasKilled = vpnShouldBeRunning && ! isVpnActive() && ! appCrashed && ! isProbablyEmulator
7481 if (vpnWasKilled) {
@@ -94,10 +101,10 @@ class HttpToolkitApplication : Application() {
94101 /* *
95102 * Grab any first run params, drop them for future usage, and return them.
96103 * This will return first-run params at most once (per install).
97- */
104+ */
98105 suspend fun popFirstRunParams (): String? {
99106 val isFirstRun = prefs.getBoolean(FIRST_RUN_PREF , true )
100- prefs.edit(). putBoolean(FIRST_RUN_PREF , false ). apply ()
107+ prefs.edit { putBoolean(FIRST_RUN_PREF , false ) }
101108
102109 val installTime = packageManager.getPackageInfo(packageName, 0 ).firstInstallTime
103110 val now = System .currentTimeMillis()
@@ -128,6 +135,7 @@ class HttpToolkitApplication : Application() {
128135 Log .i(TAG , " Returning first run referrer: $referrer " )
129136 resume(referrer)
130137 }
138+
131139 else -> {
132140 Log .w(TAG , " Couldn't get install referrer, skipping: $responseCode " )
133141 resume(null )
@@ -146,14 +154,15 @@ class HttpToolkitApplication : Application() {
146154 var lastProxy: ProxyConfig ?
147155 get() {
148156 Log .i(TAG , " Loading last proxy config" )
149- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
150- val serialized = prefs.getString(" last-proxy-config " , null )
157+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
158+ val serialized = prefs.getString(LAST_PROXY_CONFIG_PREF_KEY , null )
151159
152160 return when {
153161 serialized != null -> {
154162 Log .i(TAG , " Found last proxy config: $serialized " )
155163 Klaxon ().converter(CertificateConverter ).parse<ProxyConfig >(serialized)
156164 }
165+
157166 else -> {
158167 Log .i(TAG , " No proxy config found" )
159168 null
@@ -162,19 +171,19 @@ class HttpToolkitApplication : Application() {
162171 }
163172 set(proxyConfig) {
164173 Log .i(TAG , if (proxyConfig == null ) " Clearing proxy config" else " Saving proxy config" )
165- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
174+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
166175
167176 if (proxyConfig != null ) {
168177 val serialized = Klaxon ().converter(CertificateConverter ).toJsonString(proxyConfig)
169- prefs.edit(). putString(" last-proxy-config " , serialized). apply ()
178+ prefs.edit { putString(LAST_PROXY_CONFIG_PREF_KEY , serialized) }
170179 } else {
171- prefs.edit(). remove(" last-proxy-config " ). apply ()
180+ prefs.edit { remove(LAST_PROXY_CONFIG_PREF_KEY ) }
172181 }
173182 }
174183
175184 var uninterceptedApps: Set <String >
176185 get() {
177- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
186+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
178187 val packagesSet = prefs.getStringSet(" unintercepted-packages" , null )
179188 val allPackages = packageManager.getInstalledPackages(PackageManager .GET_META_DATA )
180189 .map { pkg -> pkg.packageName }
@@ -183,20 +192,20 @@ class HttpToolkitApplication : Application() {
183192 .toSet()
184193 }
185194 set(packageNames) {
186- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
187- prefs.edit(). putStringSet(" unintercepted-packages" , packageNames). apply ()
195+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
196+ prefs.edit { putStringSet(" unintercepted-packages" , packageNames) }
188197 }
189198
190199 var interceptedPorts: Set <Int >
191200 get() {
192- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
201+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
193202 val portsSet = prefs.getStringSet(" intercepted-ports" , null )
194203 return portsSet?.map(String ::toInt)?.toSortedSet()
195204 ? : DEFAULT_PORTS
196205 }
197206 set(ports) {
198- val prefs = getSharedPreferences(" tech.httptoolkit.android " , MODE_PRIVATE )
199- prefs.edit(). putStringSet(" intercepted-ports" , ports.map(Int ::toString).toSet()). apply ()
207+ val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME , MODE_PRIVATE )
208+ prefs.edit { putStringSet(" intercepted-ports" , ports.map(Int ::toString).toSet()) }
200209 }
201210
202211 suspend fun isUpdateRequired (): Boolean {
@@ -208,7 +217,8 @@ class HttpToolkitApplication : Application() {
208217 return @withContext false
209218 }
210219
211- val lastUpdateTime = prefs.getLong(LAST_UPDATE_CHECK_TIME_PREF ,
220+ val lastUpdateTime = prefs.getLong(
221+ LAST_UPDATE_CHECK_TIME_PREF ,
212222 firstInstallTime(this @HttpToolkitApplication)
213223 )
214224
@@ -230,9 +240,11 @@ class HttpToolkitApplication : Application() {
230240 val release = Klaxon ().parse<GithubRelease >(response)!!
231241 val releaseVersion =
232242 tryParseSemver(release.name)
233- ? : tryParseSemver(release.tag_name)
234- ? : throw RuntimeException (" Could not parse release version ${release.tag_name} " )
235- val releaseDate = SimpleDateFormat (" yyyy-MM-dd'T'HH:mm:ss" ).parse(release.published_at)!!
243+ ? : tryParseSemver(release.tagName)
244+ ? : throw RuntimeException (" Could not parse release version ${release.tagName} " )
245+
246+ val releaseDate =
247+ SimpleDateFormat (TIME_PATTERN , Locale .getDefault()).parse(release.publishedAt)!!
236248
237249 val installedVersion = getInstalledVersion(this @HttpToolkitApplication)
238250
@@ -242,7 +254,8 @@ class HttpToolkitApplication : Application() {
242254 // series of releases. Better to start chasing users only after a week stable.
243255 val updateNotTooRecent = releaseDate.before(daysAgo(7 ))
244256
245- Log .i(TAG ,
257+ Log .i(
258+ TAG ,
246259 if (updateAvailable && updateNotTooRecent)
247260 " New version available, released > 1 week"
248261 else if (updateAvailable)
@@ -251,7 +264,7 @@ class HttpToolkitApplication : Application() {
251264 " App is up to date"
252265 )
253266
254- prefs.edit(). putLong(LAST_UPDATE_CHECK_TIME_PREF , System .currentTimeMillis()). apply ()
267+ prefs.edit { putLong(LAST_UPDATE_CHECK_TIME_PREF , System .currentTimeMillis()) }
255268 return @withContext updateAvailable && updateNotTooRecent
256269 } catch (e: Exception ) {
257270 Log .w(TAG , e)
@@ -263,17 +276,21 @@ class HttpToolkitApplication : Application() {
263276}
264277
265278private fun wasInstalledFromStore (context : Context ): Boolean {
266- return context.packageManager.getInstallerPackageName(context.packageName) != null
279+ return if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
280+ context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName != null
281+ } else {
282+ context.packageManager.getInstallerPackageName(context.packageName) != null
283+ }
267284}
268285
269286private fun firstInstallTime (context : Context ): Long {
270287 return context.packageManager.getPackageInfo(context.packageName, 0 ).firstInstallTime
271288}
272289
273290private data class GithubRelease (
274- val tag_name : String? ,
291+ val tagName : String? ,
275292 val name : String? ,
276- val published_at : String
293+ val publishedAt : String ,
277294)
278295
279296private fun tryParseSemver (version : String? ): SemVer ? = try {
@@ -296,4 +313,4 @@ private fun daysAgo(days: Int): Date {
296313 val calendar = Calendar .getInstance()
297314 calendar.add(Calendar .DAY_OF_YEAR , - days)
298315 return calendar.time
299- }
316+ }
0 commit comments