Skip to content

Commit 5963c3c

Browse files
author
itananaev
committed
Refactor: Use Android KTX extensions
This commit refactors the codebase to leverage Android KTX extensions for more idiomatic Kotlin code. Specific changes include: - Using `String.toUri()` for `Uri` parsing. - Using `SharedPreferences.edit { ... }` for simplified SharedPreferences editing. - Using `View.isVisible` for managing view visibility. - Updating `getInstallerPackageName` usage to `getInstallSourceInfo` for Android R and above. - Removing XML declaration from `.idea/misc.xml`.
1 parent c8795d4 commit 5963c3c

File tree

3 files changed

+69
-51
lines changed

3 files changed

+69
-51
lines changed

app/src/main/java/tech/httptoolkit/android/ConnectionStatusView.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import android.widget.LinearLayout
1010
import android.widget.TextView
1111
import com.google.android.material.card.MaterialCardView
1212
import com.google.android.material.dialog.MaterialAlertDialogBuilder
13+
import androidx.core.net.toUri
1314

1415
private val isLineageOs = Build.HOST.startsWith("lineage")
1516

@@ -71,7 +72,7 @@ class ConnectionStatusView(
7172
.setPositiveButton("View the bug") { _, _ ->
7273
context.startActivity(Intent(
7374
Intent.ACTION_VIEW,
74-
Uri.parse("https://gitlab.com/LineageOS/issues/android/-/issues/1706")
75+
"https://gitlab.com/LineageOS/issues/android/-/issues/1706".toUri()
7576
))
7677
}
7778
.show()
@@ -104,4 +105,4 @@ class ConnectionStatusView(
104105
)
105106
}
106107

107-
}
108+
}

app/src/main/java/tech/httptoolkit/android/HttpToolkitApplication.kt

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package tech.httptoolkit.android
22

33
import android.app.Application
4-
import android.content.*
4+
import android.content.Context
5+
import android.content.SharedPreferences
56
import android.content.pm.PackageManager
67
import android.os.Build
78
import android.util.Log
9+
import androidx.core.content.edit
810
import com.android.installreferrer.api.InstallReferrerClient
911
import com.android.installreferrer.api.InstallReferrerClient.InstallReferrerResponse
1012
import com.android.installreferrer.api.InstallReferrerStateListener
@@ -16,7 +18,9 @@ import net.swiftzer.semver.SemVer
1618
import okhttp3.OkHttpClient
1719
import okhttp3.Request
1820
import java.text.SimpleDateFormat
19-
import java.util.*
21+
import java.util.Calendar
22+
import java.util.Date
23+
import java.util.Locale
2024
import java.util.concurrent.atomic.AtomicBoolean
2125
import kotlin.coroutines.resume
2226
import kotlin.coroutines.suspendCoroutine
@@ -25,19 +29,22 @@ private const val VPN_START_TIME_PREF = "vpn-start-time"
2529
private const val LAST_UPDATE_CHECK_TIME_PREF = "update-check-time"
2630
private const val APP_CRASHED_PREF = "app-crashed"
2731
private 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

2936
private 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

4249
private 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

265278
private 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

269286
private fun firstInstallTime(context: Context): Long {
270287
return context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTime
271288
}
272289

273290
private 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

279296
private 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+
}

app/src/main/java/tech/httptoolkit/android/MainActivity.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import java.net.ConnectException
3939
import java.net.SocketTimeoutException
4040
import java.security.cert.Certificate
4141
import java.security.cert.X509Certificate
42+
import androidx.core.view.isVisible
43+
import androidx.core.net.toUri
4244

4345

4446
const val START_VPN_REQUEST = 123
@@ -373,7 +375,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
373375
}
374376
}
375377

376-
if (buttonContainer.visibility == View.VISIBLE) {
378+
if (buttonContainer.isVisible) {
377379
buttonContainer.addView(secondaryButton(R.string.docs_button, ::openDocs))
378380
}
379381
}
@@ -525,9 +527,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
525527
startActivity(
526528
Intent(
527529
Intent.ACTION_VIEW,
528-
Uri.parse((
529-
if (canUseHttps) "https" else "http"
530-
) + "://" + uri)
530+
((if (canUseHttps) "https" else "http") + "://" + uri).toUri()
531531
).apply {
532532
if (browserPackage != null) setPackage(browserPackage)
533533
}
@@ -631,7 +631,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
631631
}
632632

633633
private suspend fun connectToVpnFromUrl(url: String) {
634-
connectToVpnFromUrl(Uri.parse(url))
634+
connectToVpnFromUrl(url.toUri())
635635
}
636636

637637
private suspend fun connectToVpnFromUrl(uri: Uri) {
@@ -930,7 +930,7 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
930930
// it is imminent, and installing from play means it'll update fully later.
931931
startActivity(
932932
Intent(Intent.ACTION_VIEW).apply {
933-
data = Uri.parse("market://details?id=tech.httptoolkit.android.v1")
933+
data = "market://details?id=tech.httptoolkit.android.v1".toUri()
934934
}
935935
)
936936
}
@@ -1036,7 +1036,7 @@ private fun isPackageAvailable(context: Context, packageName: String) = try {
10361036
}
10371037

10381038
private fun getDefaultBrowserPackage(context: Context): String? {
1039-
val browserIntent = Intent("android.intent.action.VIEW", Uri.parse("http://example.com"))
1039+
val browserIntent = Intent("android.intent.action.VIEW", "http://example.com".toUri())
10401040
val resolveInfo = context.packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
10411041
return resolveInfo?.activityInfo?.packageName
10421042
}
@@ -1068,4 +1068,4 @@ private fun getTestBrowserPackage(context: Context): String? {
10681068
}
10691069

10701070
private fun isStoreAvailable(context: Context): Boolean =
1071-
isPackageAvailable(context, GooglePlayServicesUtil.GOOGLE_PLAY_STORE_PACKAGE)
1071+
isPackageAvailable(context, GooglePlayServicesUtil.GOOGLE_PLAY_STORE_PACKAGE)

0 commit comments

Comments
 (0)