Skip to content

Commit e1e062b

Browse files
authored
Merge pull request #36 from WiMank/ref-code-and-style
Refactor: Use Android KTX extensions
2 parents c8795d4 + 0cb879d commit e1e062b

File tree

3 files changed

+73
-51
lines changed

3 files changed

+73
-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: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
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
13+
import com.beust.klaxon.Json
1114
import com.beust.klaxon.Klaxon
1215
import io.sentry.Sentry
1316
import kotlinx.coroutines.Dispatchers
@@ -16,7 +19,9 @@ import net.swiftzer.semver.SemVer
1619
import okhttp3.OkHttpClient
1720
import okhttp3.Request
1821
import java.text.SimpleDateFormat
19-
import java.util.*
22+
import java.util.Calendar
23+
import java.util.Date
24+
import java.util.Locale
2025
import java.util.concurrent.atomic.AtomicBoolean
2126
import kotlin.coroutines.resume
2227
import kotlin.coroutines.suspendCoroutine
@@ -25,19 +30,22 @@ private const val VPN_START_TIME_PREF = "vpn-start-time"
2530
private const val LAST_UPDATE_CHECK_TIME_PREF = "update-check-time"
2631
private const val APP_CRASHED_PREF = "app-crashed"
2732
private const val FIRST_RUN_PREF = "is-first-run"
33+
private const val HTTP_TOOLKIT_PREFERENCES_NAME = "tech.httptoolkit.android"
34+
private const val LAST_PROXY_CONFIG_PREF_KEY = "last-proxy-config"
35+
private const val TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"
2836

2937
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"
38+
Build.FINGERPRINT.startsWith("generic")
39+
|| Build.FINGERPRINT.startsWith("unknown")
40+
|| Build.MODEL.contains("google_sdk")
41+
|| Build.MODEL.contains("sdk_gphone")
42+
|| Build.MODEL.contains("Emulator")
43+
|| Build.MODEL.contains("Android SDK built for x86")
44+
|| Build.BOARD == "QC_Reference_Phone"
45+
|| Build.MANUFACTURER.contains("Genymotion")
46+
|| Build.HOST.startsWith("Build")
47+
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
48+
|| Build.PRODUCT == "google_sdk"
4149

4250
private val bootTime = (System.currentTimeMillis() - android.os.SystemClock.elapsedRealtime())
4351

@@ -52,23 +60,23 @@ class HttpToolkitApplication : Application() {
5260
}
5361
set(value) {
5462
if (value) {
55-
prefs.edit().putLong(VPN_START_TIME_PREF, System.currentTimeMillis()).apply()
63+
prefs.edit { putLong(VPN_START_TIME_PREF, System.currentTimeMillis()) }
5664
} else {
57-
prefs.edit().putLong(VPN_START_TIME_PREF, -1).apply()
65+
prefs.edit { putLong(VPN_START_TIME_PREF, -1) }
5866
}
5967
}
6068

6169
override fun onCreate() {
6270
super.onCreate()
63-
prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
71+
prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
6472

6573
Thread.setDefaultUncaughtExceptionHandler { _, _ ->
66-
prefs.edit().putBoolean(APP_CRASHED_PREF, true).apply()
74+
prefs.edit { putBoolean(APP_CRASHED_PREF, true) }
6775
}
6876

6977
// Check if we've been recreated unexpectedly, with no crashes in the meantime:
7078
val appCrashed = prefs.getBoolean(APP_CRASHED_PREF, false)
71-
prefs.edit().putBoolean(APP_CRASHED_PREF, false).apply()
79+
prefs.edit { putBoolean(APP_CRASHED_PREF, false) }
7280

7381
vpnWasKilled = vpnShouldBeRunning && !isVpnActive() && !appCrashed && !isProbablyEmulator
7482
if (vpnWasKilled) {
@@ -94,10 +102,10 @@ class HttpToolkitApplication : Application() {
94102
/**
95103
* Grab any first run params, drop them for future usage, and return them.
96104
* This will return first-run params at most once (per install).
97-
*/
105+
*/
98106
suspend fun popFirstRunParams(): String? {
99107
val isFirstRun = prefs.getBoolean(FIRST_RUN_PREF, true)
100-
prefs.edit().putBoolean(FIRST_RUN_PREF, false).apply()
108+
prefs.edit { putBoolean(FIRST_RUN_PREF, false) }
101109

102110
val installTime = packageManager.getPackageInfo(packageName, 0).firstInstallTime
103111
val now = System.currentTimeMillis()
@@ -128,6 +136,7 @@ class HttpToolkitApplication : Application() {
128136
Log.i(TAG, "Returning first run referrer: $referrer")
129137
resume(referrer)
130138
}
139+
131140
else -> {
132141
Log.w(TAG, "Couldn't get install referrer, skipping: $responseCode")
133142
resume(null)
@@ -146,14 +155,15 @@ class HttpToolkitApplication : Application() {
146155
var lastProxy: ProxyConfig?
147156
get() {
148157
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)
158+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
159+
val serialized = prefs.getString(LAST_PROXY_CONFIG_PREF_KEY, null)
151160

152161
return when {
153162
serialized != null -> {
154163
Log.i(TAG, "Found last proxy config: $serialized")
155164
Klaxon().converter(CertificateConverter).parse<ProxyConfig>(serialized)
156165
}
166+
157167
else -> {
158168
Log.i(TAG, "No proxy config found")
159169
null
@@ -162,19 +172,19 @@ class HttpToolkitApplication : Application() {
162172
}
163173
set(proxyConfig) {
164174
Log.i(TAG, if (proxyConfig == null) "Clearing proxy config" else "Saving proxy config")
165-
val prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
175+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
166176

167177
if (proxyConfig != null) {
168178
val serialized = Klaxon().converter(CertificateConverter).toJsonString(proxyConfig)
169-
prefs.edit().putString("last-proxy-config", serialized).apply()
179+
prefs.edit { putString(LAST_PROXY_CONFIG_PREF_KEY, serialized) }
170180
} else {
171-
prefs.edit().remove("last-proxy-config").apply()
181+
prefs.edit { remove(LAST_PROXY_CONFIG_PREF_KEY) }
172182
}
173183
}
174184

175185
var uninterceptedApps: Set<String>
176186
get() {
177-
val prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
187+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
178188
val packagesSet = prefs.getStringSet("unintercepted-packages", null)
179189
val allPackages = packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
180190
.map { pkg -> pkg.packageName }
@@ -183,20 +193,20 @@ class HttpToolkitApplication : Application() {
183193
.toSet()
184194
}
185195
set(packageNames) {
186-
val prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
187-
prefs.edit().putStringSet("unintercepted-packages", packageNames).apply()
196+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
197+
prefs.edit { putStringSet("unintercepted-packages", packageNames) }
188198
}
189199

190200
var interceptedPorts: Set<Int>
191201
get() {
192-
val prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
202+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
193203
val portsSet = prefs.getStringSet("intercepted-ports", null)
194204
return portsSet?.map(String::toInt)?.toSortedSet()
195205
?: DEFAULT_PORTS
196206
}
197207
set(ports) {
198-
val prefs = getSharedPreferences("tech.httptoolkit.android", MODE_PRIVATE)
199-
prefs.edit().putStringSet("intercepted-ports", ports.map(Int::toString).toSet()).apply()
208+
val prefs = getSharedPreferences(HTTP_TOOLKIT_PREFERENCES_NAME, MODE_PRIVATE)
209+
prefs.edit { putStringSet("intercepted-ports", ports.map(Int::toString).toSet()) }
200210
}
201211

202212
suspend fun isUpdateRequired(): Boolean {
@@ -208,7 +218,8 @@ class HttpToolkitApplication : Application() {
208218
return@withContext false
209219
}
210220

211-
val lastUpdateTime = prefs.getLong(LAST_UPDATE_CHECK_TIME_PREF,
221+
val lastUpdateTime = prefs.getLong(
222+
LAST_UPDATE_CHECK_TIME_PREF,
212223
firstInstallTime(this@HttpToolkitApplication)
213224
)
214225

@@ -230,9 +241,11 @@ class HttpToolkitApplication : Application() {
230241
val release = Klaxon().parse<GithubRelease>(response)!!
231242
val releaseVersion =
232243
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)!!
244+
?: tryParseSemver(release.tagName)
245+
?: throw RuntimeException("Could not parse release version ${release.tagName}")
246+
247+
val releaseDate =
248+
SimpleDateFormat(TIME_PATTERN, Locale.getDefault()).parse(release.publishedAt)!!
236249

237250
val installedVersion = getInstalledVersion(this@HttpToolkitApplication)
238251

@@ -242,7 +255,8 @@ class HttpToolkitApplication : Application() {
242255
// series of releases. Better to start chasing users only after a week stable.
243256
val updateNotTooRecent = releaseDate.before(daysAgo(7))
244257

245-
Log.i(TAG,
258+
Log.i(
259+
TAG,
246260
if (updateAvailable && updateNotTooRecent)
247261
"New version available, released > 1 week"
248262
else if (updateAvailable)
@@ -251,7 +265,7 @@ class HttpToolkitApplication : Application() {
251265
"App is up to date"
252266
)
253267

254-
prefs.edit().putLong(LAST_UPDATE_CHECK_TIME_PREF, System.currentTimeMillis()).apply()
268+
prefs.edit { putLong(LAST_UPDATE_CHECK_TIME_PREF, System.currentTimeMillis()) }
255269
return@withContext updateAvailable && updateNotTooRecent
256270
} catch (e: Exception) {
257271
Log.w(TAG, e)
@@ -263,17 +277,24 @@ class HttpToolkitApplication : Application() {
263277
}
264278

265279
private fun wasInstalledFromStore(context: Context): Boolean {
266-
return context.packageManager.getInstallerPackageName(context.packageName) != null
280+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
281+
context.packageManager.getInstallSourceInfo(context.packageName).installingPackageName != null
282+
} else {
283+
context.packageManager.getInstallerPackageName(context.packageName) != null
284+
}
267285
}
268286

269287
private fun firstInstallTime(context: Context): Long {
270288
return context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTime
271289
}
272290

273291
private data class GithubRelease(
274-
val tag_name: String?,
292+
@Json(name = "tag_name")
293+
val tagName: String?,
294+
@Json(name = "name")
275295
val name: String?,
276-
val published_at: String
296+
@Json(name = "published_at")
297+
val publishedAt: String,
277298
)
278299

279300
private fun tryParseSemver(version: String?): SemVer? = try {
@@ -296,4 +317,4 @@ private fun daysAgo(days: Int): Date {
296317
val calendar = Calendar.getInstance()
297318
calendar.add(Calendar.DAY_OF_YEAR, -days)
298319
return calendar.time
299-
}
320+
}

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)