Skip to content

Commit 3feedb7

Browse files
authoredFeb 3, 2025··
Merge pull request #32
Improve QR code flow & permissions, and update lots of assorted build config & Android API usage
2 parents ea0d026 + 2c206b4 commit 3feedb7

17 files changed

+396
-299
lines changed
 

‎app/build.gradle

-85
This file was deleted.

‎app/build.gradle.kts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
id("kotlin-parcelize")
5+
}
6+
7+
android {
8+
namespace = "tech.httptoolkit.android"
9+
compileSdk = 35
10+
11+
defaultConfig {
12+
applicationId = "tech.httptoolkit.android.v1"
13+
minSdk = 21
14+
targetSdk = 34
15+
versionCode = 34
16+
versionName = "1.5.0"
17+
18+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19+
20+
manifestPlaceholders["sentryEnabled"] = "false"
21+
manifestPlaceholders["sentryDsn"] = "null"
22+
}
23+
24+
buildTypes {
25+
release {
26+
isMinifyEnabled = false
27+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
28+
29+
manifestPlaceholders["sentryEnabled"] = "true"
30+
manifestPlaceholders["sentryDsn"] = "https://6943ce7476d54485a5998ad45289a9bc@sentry.io/1809979"
31+
}
32+
}
33+
34+
35+
compileOptions {
36+
sourceCompatibility = JavaVersion.VERSION_11
37+
targetCompatibility = JavaVersion.VERSION_11
38+
}
39+
40+
kotlinOptions {
41+
jvmTarget = "11"
42+
}
43+
44+
buildFeatures {
45+
viewBinding = true
46+
buildConfig = true
47+
}
48+
lint {
49+
lintConfig = file("./lint.xml")
50+
}
51+
}
52+
53+
dependencies {
54+
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
55+
implementation(libs.kotlin.stdlib.jdk7)
56+
implementation(libs.kotlin.reflect)
57+
implementation(libs.kotlinx.coroutines.core)
58+
implementation(libs.kotlinx.coroutines.android)
59+
implementation(libs.appcompat)
60+
implementation(libs.core.ktx)
61+
implementation(libs.constraintlayout)
62+
implementation(libs.localbroadcastmanager)
63+
implementation(libs.zxing.android.embedded) { isTransitive = false }
64+
implementation(libs.core)
65+
implementation(libs.klaxon)
66+
implementation(libs.okhttp)
67+
implementation(libs.material)
68+
implementation(libs.semver)
69+
implementation(libs.sentry.android)
70+
implementation(libs.slf4j.nop)
71+
implementation(libs.play.services.base)
72+
implementation(libs.installreferrer)
73+
implementation(libs.swiperefreshlayout)
74+
testImplementation(libs.junit)
75+
androidTestImplementation(libs.runner)
76+
androidTestImplementation(libs.espresso.core)
77+
}

‎app/src/main/AndroidManifest.xml

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<application
3333
android:name=".HttpToolkitApplication"
3434
android:allowBackup="true"
35+
android:hardwareAccelerated="true"
3536
android:icon="@mipmap/ic_launcher"
3637
android:label="@string/app_name"
3738
android:roundIcon="@mipmap/ic_launcher_round"
@@ -100,7 +101,7 @@
100101
</intent-filter>
101102
</activity-alias>
102103

103-
<activity android:name=".ScanActivity" android:parentActivityName=".MainActivity">
104+
<activity android:name=".QRScanActivity" android:parentActivityName=".MainActivity">
104105
<meta-data
105106
android:name="android.support.PARENT_ACTIVITY"
106107
android:value=".MainActivity" />

‎app/src/main/java/tech/httptoolkit/android/ApplicationListActivity.kt

+2-7
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
1616
import kotlinx.coroutines.*
1717
import tech.httptoolkit.android.databinding.AppsListBinding
1818
import java.util.*
19-
import kotlin.collections.ArrayList
20-
21-
// Used to both to send and return the current list of selected apps
22-
const val UNSELECTED_APPS_EXTRA = "tech.httptoolkit.android.UNSELECTED_APPS_EXTRA"
2319

2420
class ApplicationListActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefreshListener,
2521
CoroutineScope by MainScope(), PopupMenu.OnMenuItemClickListener, View.OnClickListener {
@@ -38,8 +34,7 @@ class ApplicationListActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefres
3834
override fun onCreate(savedInstanceState: Bundle?) {
3935
super.onCreate(savedInstanceState)
4036

41-
blockedPackages = intent.getStringArrayExtra(UNSELECTED_APPS_EXTRA)!!.toHashSet()
42-
37+
blockedPackages = intent.getStringArrayExtra(IntentExtras.UNSELECTED_APPS_EXTRA)!!.toHashSet()
4338
binding = AppsListBinding.inflate(layoutInflater)
4439
setContentView(binding.root)
4540

@@ -60,7 +55,7 @@ class ApplicationListActivity : AppCompatActivity(), SwipeRefreshLayout.OnRefres
6055
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
6156
override fun handleOnBackPressed() {
6257
setResult(RESULT_OK, Intent().putExtra(
63-
UNSELECTED_APPS_EXTRA,
58+
IntentExtras.UNSELECTED_APPS_EXTRA,
6459
blockedPackages.toTypedArray()
6560
))
6661
finish()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tech.httptoolkit.android
2+
3+
object IntentExtras {
4+
const val SCANNED_URL_EXTRA = "tech.httptoolkit.android.SCANNED_URL"
5+
const val SELECTED_PORTS_EXTRA = "tech.httptoolkit.android.SELECTED_PORTS_EXTRA"
6+
const val UNSELECTED_APPS_EXTRA = "tech.httptoolkit.android.UNSELECTED_APPS_EXTRA"
7+
const val PROXY_CONFIG_EXTRA = "tech.httptoolkit.android.PROXY_CONFIG"
8+
const val UNINTERCEPTED_APPS_EXTRA = "tech.httptoolkit.android.UNINTERCEPTED_APPS"
9+
const val INTERCEPTED_PORTS_EXTRA = "tech.httptoolkit.android.INTERCEPTED_PORTS"
10+
}

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

+141-77
Large diffs are not rendered by default.

‎app/src/main/java/tech/httptoolkit/android/PortListActivity.kt

+4-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import androidx.activity.OnBackPressedCallback
77
import androidx.appcompat.app.AppCompatActivity
88
import androidx.appcompat.view.ContextThemeWrapper
99
import androidx.core.widget.doAfterTextChanged
10-
import kotlinx.coroutines.*
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.MainScope
1112
import tech.httptoolkit.android.databinding.PortsListBinding
1213
import java.util.*
1314

@@ -20,9 +21,6 @@ val DEFAULT_PORTS = setOf(
2021
const val MIN_PORT = 1
2122
const val MAX_PORT = 65535
2223

23-
// Used to both to send and return the current list of selected ports
24-
const val SELECTED_PORTS_EXTRA = "tech.httptoolkit.android.SELECTED_PORTS_EXTRA"
25-
2624
class PortListActivity : AppCompatActivity(), CoroutineScope by MainScope() {
2725

2826
private lateinit var ports: TreeSet<Int> // TreeSet = Mutable + Sorted
@@ -35,7 +33,7 @@ class PortListActivity : AppCompatActivity(), CoroutineScope by MainScope() {
3533
binding = PortsListBinding.inflate(layoutInflater)
3634
setContentView(binding.root)
3735

38-
ports = intent.getIntArrayExtra(SELECTED_PORTS_EXTRA)!!
36+
ports = intent.getIntArrayExtra(IntentExtras.SELECTED_PORTS_EXTRA)!!
3937
.toCollection(TreeSet())
4038

4139
binding.portsListRecyclerView.adapter =
@@ -84,7 +82,7 @@ class PortListActivity : AppCompatActivity(), CoroutineScope by MainScope() {
8482
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
8583
override fun handleOnBackPressed() {
8684
setResult(RESULT_OK, Intent().putExtra(
87-
SELECTED_PORTS_EXTRA,
85+
IntentExtras.SELECTED_PORTS_EXTRA,
8886
ports.toIntArray()
8987
))
9088
finish()

‎app/src/main/java/tech/httptoolkit/android/ProxyVpnService.kt

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
package tech.httptoolkit.android
22

3-
import android.net.VpnService
4-
import android.content.Intent
53
import android.app.*
4+
import android.content.Intent
65
import android.content.pm.PackageManager
76
import android.graphics.BitmapFactory
87
import android.net.ProxyInfo
8+
import android.net.VpnService
99
import android.os.Build
1010
import android.os.ParcelFileDescriptor
1111
import android.util.Log
1212
import androidx.core.app.NotificationCompat
1313
import androidx.localbroadcastmanager.content.LocalBroadcastManager
14+
import io.sentry.Sentry
1415
import tech.httptoolkit.android.vpn.socket.IProtectSocket
1516
import tech.httptoolkit.android.vpn.socket.SocketProtector
16-
import io.sentry.Sentry
17-
import java.io.*
17+
import java.io.IOException
1818

1919
private const val ALL_ROUTES = "0.0.0.0"
2020
private const val VPN_IP_ADDRESS = "169.254.61.43" // Random link-local IP, this will be the tunnel's IP
@@ -28,10 +28,6 @@ const val STOP_VPN_ACTION = "tech.httptoolkit.android.STOP_VPN_ACTION"
2828
const val VPN_STARTED_BROADCAST = "tech.httptoolkit.android.VPN_STARTED_BROADCAST"
2929
const val VPN_STOPPED_BROADCAST = "tech.httptoolkit.android.VPN_STOPPED_BROADCAST"
3030

31-
const val PROXY_CONFIG_EXTRA = "tech.httptoolkit.android.PROXY_CONFIG"
32-
const val UNINTERCEPTED_APPS_EXTRA = "tech.httptoolkit.android.UNINTERCEPTED_APPS"
33-
const val INTERCEPTED_PORTS_EXTRA = "tech.httptoolkit.android.INTERCEPTED_PORTS"
34-
3531
private var currentService: ProxyVpnService? = null
3632
fun isVpnActive(): Boolean {
3733
return if (currentService == null)
@@ -77,9 +73,9 @@ class ProxyVpnService : VpnService(), IProtectSocket {
7773
app = this.application as HttpToolkitApplication
7874

7975
if (intent.action == START_VPN_ACTION) {
80-
val proxyConfig = intent.getParcelableExtra<ProxyConfig>(PROXY_CONFIG_EXTRA)!!
81-
val uninterceptedApps = intent.getStringArrayExtra(UNINTERCEPTED_APPS_EXTRA)!!.toSet()
82-
val interceptedPorts = intent.getIntArrayExtra(INTERCEPTED_PORTS_EXTRA)!!.toSet()
76+
val proxyConfig = intent.getParcelableExtra<ProxyConfig>(IntentExtras.PROXY_CONFIG_EXTRA)!!
77+
val uninterceptedApps = intent.getStringArrayExtra(IntentExtras.UNINTERCEPTED_APPS_EXTRA)!!.toSet()
78+
val interceptedPorts = intent.getIntArrayExtra(IntentExtras.INTERCEPTED_PORTS_EXTRA)!!.toSet()
8379

8480
val vpnStarted = if (isActive())
8581
restartVpn(proxyConfig, uninterceptedApps, interceptedPorts)
@@ -241,7 +237,7 @@ class ProxyVpnService : VpnService(), IProtectSocket {
241237
showServiceNotification()
242238
localBroadcastManager!!.sendBroadcast(
243239
Intent(VPN_STARTED_BROADCAST).apply {
244-
putExtra(PROXY_CONFIG_EXTRA, proxyConfig)
240+
putExtra(IntentExtras.PROXY_CONFIG_EXTRA, proxyConfig)
245241
}
246242
)
247243

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package tech.httptoolkit.android
2+
3+
import android.app.Activity
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import android.util.Log
7+
import android.view.KeyEvent
8+
import com.google.zxing.BarcodeFormat
9+
import com.google.zxing.ResultPoint
10+
import com.journeyapps.barcodescanner.BarcodeCallback
11+
import com.journeyapps.barcodescanner.BarcodeResult
12+
import com.journeyapps.barcodescanner.DecoratedBarcodeView
13+
import com.journeyapps.barcodescanner.DefaultDecoderFactory
14+
15+
class QRScanActivity : Activity() {
16+
private var barcodeView: DecoratedBarcodeView? = null
17+
private var lastText: String? = null
18+
19+
private val callback: BarcodeCallback = object : BarcodeCallback {
20+
override fun barcodeResult(result: BarcodeResult) {
21+
val resultText = result.text
22+
if (resultText == null || resultText == lastText) {
23+
// Prevent duplicate scans
24+
return
25+
}
26+
27+
lastText = resultText
28+
Log.i("QRScanActivity", "Scanned: $resultText")
29+
30+
if (lastText!!.startsWith("https://android.httptoolkit.tech/connect/")) {
31+
setResult(RESULT_OK, Intent().putExtra(IntentExtras.SCANNED_URL_EXTRA, lastText))
32+
finish()
33+
}
34+
}
35+
36+
override fun possibleResultPoints(resultPoints: MutableList<ResultPoint?>?) {
37+
}
38+
}
39+
40+
override fun onCreate(savedInstanceState: Bundle?) {
41+
super.onCreate(savedInstanceState)
42+
setContentView(R.layout.qr_scan_activity)
43+
barcodeView = findViewById<DecoratedBarcodeView>(R.id.barcode_scanner)
44+
barcodeView!!.barcodeView.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE))
45+
barcodeView!!.initializeFromIntent(intent)
46+
barcodeView!!.decodeContinuous(callback)
47+
barcodeView!!.setStatusText("Scan HTTPToolkit QR code to connect")
48+
}
49+
50+
override fun onResume() {
51+
super.onResume()
52+
barcodeView!!.resume()
53+
}
54+
55+
override fun onPause() {
56+
super.onPause()
57+
barcodeView!!.pause()
58+
}
59+
60+
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
61+
return barcodeView!!.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
62+
}
63+
}

‎app/src/main/java/tech/httptoolkit/android/ScanActivity.kt

-83
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<com.journeyapps.barcodescanner.DecoratedBarcodeView android:id="@+id/barcode_scanner"
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:layout_alignParentTop="true">
7+
8+
</com.journeyapps.barcodescanner.DecoratedBarcodeView>

‎app/src/main/res/values/strings.xml

+3
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,7 @@
5454
<string name="add_port_prompt">Add another port</string>
5555
<string name="reset_to_default_ports">Reset to default ports</string>
5656
<string name="port_config_explanation">HTTP Toolkit sets the device\'s HTTP proxy configuration, to capture traffic from all apps on any port, but some apps may ignore this.\n\nYou can add ports here to forcibly redirect all outgoing traffic to that port, but be careful, as this will interfere with any non-HTTP traffic on these ports.</string>
57+
<string name="cancel">Cancel</string>
58+
<string name="proceed">Proceed</string>
59+
<string name="open_settings">Open Settings</string>
5760
</resources>

‎build.gradle

-27
This file was deleted.

‎build.gradle.kts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
2+
plugins {
3+
alias(libs.plugins.android.application) apply false
4+
alias(libs.plugins.kotlin.android) apply false
5+
alias(libs.plugins.google.services) apply false
6+
}

‎gradle/libs.versions.toml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[versions]
2+
kotlin = "1.9.25"
3+
androidGradlePlugin = "8.7.3"
4+
appcompat = "1.7.0"
5+
constraintlayout = "2.2.0"
6+
core = "3.4.1"
7+
coreKtx = "1.15.0"
8+
espressoCore = "3.6.1"
9+
googleServices = "4.4.2"
10+
installreferrer = "2.2"
11+
junit = "4.13.2"
12+
klaxon = "5.5"
13+
kotlinxCoroutinesCore = "1.8.1"
14+
localbroadcastmanager = "1.1.0"
15+
material = "1.12.0"
16+
okhttp = "4.11.0"
17+
playServicesBase = "18.5.0"
18+
runner = "1.6.2"
19+
semver = "1.1.1"
20+
sentryAndroid = "8.0.0"
21+
slf4jNop = "1.7.25"
22+
zxingAndroidEmbedded = "4.3.0"
23+
swiperefreshlayout = "1.1.0"
24+
25+
[libraries]
26+
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
27+
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
28+
core = { module = "com.google.zxing:core", version.ref = "core" }
29+
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
30+
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
31+
installreferrer = { module = "com.android.installreferrer:installreferrer", version.ref = "installreferrer" }
32+
junit = { module = "junit:junit", version.ref = "junit" }
33+
klaxon = { module = "com.beust:klaxon", version.ref = "klaxon" }
34+
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
35+
kotlin-stdlib-jdk7 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
36+
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }
37+
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
38+
localbroadcastmanager = { module = "androidx.localbroadcastmanager:localbroadcastmanager", version.ref = "localbroadcastmanager" }
39+
material = { module = "com.google.android.material:material", version.ref = "material" }
40+
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
41+
play-services-base = { module = "com.google.android.gms:play-services-base", version.ref = "playServicesBase" }
42+
runner = { module = "androidx.test:runner", version.ref = "runner" }
43+
semver = { module = "net.swiftzer.semver:semver", version.ref = "semver" }
44+
sentry-android = { module = "io.sentry:sentry-android", version.ref = "sentryAndroid" }
45+
slf4j-nop = { module = "org.slf4j:slf4j-nop", version.ref = "slf4jNop" }
46+
swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" }
47+
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
48+
49+
[plugins]
50+
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
51+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
52+
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }

‎settings.gradle

-1
This file was deleted.

‎settings.gradle.kts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pluginManagement {
2+
repositories {
3+
google {
4+
content {
5+
includeGroupByRegex("com\\.android.*")
6+
includeGroupByRegex("com\\.google.*")
7+
includeGroupByRegex("androidx.*")
8+
}
9+
}
10+
mavenCentral()
11+
}
12+
}
13+
dependencyResolutionManagement {
14+
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
15+
repositories {
16+
google()
17+
mavenCentral()
18+
}
19+
}
20+
include(":app")

0 commit comments

Comments
 (0)
Please sign in to comment.