Full project refactor: dead-code purge + Kotlin/Compose modernization + Rust JNI hardening + build tooling#45
Merged
Merged
Conversation
Both legacy components are obsolete after the VPN-first netstack-smoltcp rewrite: - libevent (45K LOC) was only consumed by redsocks - redsocks (transparent iptables proxy) is unreachable from the Compose UI / VPN service path No Kotlin/Java code references either tree. CMakeLists root no longer descends into them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
minSdk is 24, so -v9 and -v11 resource qualifier buckets are dead.
The four ic_stat_proxydroid.png copies in each are superseded by
the unqualified drawable-{hdpi,ldpi,mdpi,xhdpi}/ variants still
referenced by ProxyDroidVpnService.
button_gray/white/selector.xml were only self-referential; no
layout or Compose code consumes them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the entire root command-execution surface that the VPN-first architecture no longer needs: - app/src/main/cpp/exec/termExec.cpp + CMakeLists.txt (createSubprocess/waitFor JNI bindings for su shell) - app/src/main/java/org/proxydroid/Exec.kt (Kotlin companion to termExec.cpp) - Utils.kt: checkRoot, runRootCommand, runScript, getHasRedirectSupport, getIptablesPath, getShellPath, isRoot field + setRoot/isRoot accessors None of these have callers outside Utils.kt itself; the Compose UI, MainViewModel, ProxyDroidVpnService, ConnectivityBroadcastReceiver and ProxyDroidWidgetProvider only use Utils.isWorking/setWorking and Utils.isConnecting/setConnecting, which are preserved. cpp/CMakeLists.txt keeps a single placeholder.c target so AGP's externalNativeBuild CMake configure step still has something to build until app/build.gradle drops externalNativeBuild entirely (owned by the build-tooling agent). Build verified: ./gradlew :app:assembleDebug -> BUILD SUCCESSFUL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move all dependency and plugin version literals from build.gradle and app/build.gradle into a standard Gradle version catalog. Group app dependencies into androidx core / Kotlin / Compose / third-party / test sections with brief comments. AGP 8.1.2, kotlin 1.9.10, rust-android 0.9.6 and compose-compiler 1.5.3 are now declared in one place. No version bumps — pure refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the commented-out org.gradle.parallel scaffold and verbose doc boilerplate inherited from `android create-project`. Keep every active setting documented; preserve the AGP 8.1.2 compileSdk-36 suppression block verbatim per refactor brief. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alog - Remove the four 'Create dummy google-services.json' steps. The app has no Firebase/GMS plugin and no code path reads google-services.json; they were inherited scaffolding. - Add 'refactor/**' to push + PR branch filters so the cleanup branches exercise CI. - Include gradle/libs.versions.toml in the Gradle cache key so version catalog changes invalidate cache correctly. - Cargo + Gradle caches already in place; left unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the legacy redsocks/iptables-era README. The current reality: - VPN-only (no root, no iptables, no redsocks) - Rust tun2socks via netstack-smoltcp wired through rust-android-gradle - Jetpack Compose Material 3 UI - minSdk 24, target/compile 36, AGP 8.1.2 pinned (with rationale) PROJECT STRUCTURE section now matches the post-refactor tree (cpp/exec only, rust/proxydroid-tun2socks, gradle/libs.versions.toml) and flags the legacy libevent/redsocks dirs as scheduled for removal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The app ships no advertising, analytics, or crash-reporting SDKs — the listed third-party services are stale claims. Replace that paragraph with an accurate statement scoped to the VPN traffic itself. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…et2 dep Introduce a structured error type for the JNI seam and the top-level start/stop paths so we can stop relying on String errors and on .expect() inside ABI-exported code. Lower-level I/O continues to use std::io::Error. Also drop the socket2 direct dependency: it was only referenced by the dead protected_connect() helper (removed in a follow-up commit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- start() now returns Result<(), Tun2SocksError> instead of Result<(),
String>; doh_client::init_doh_client returns the same.
- Remove .expect() calls on the netstack-smoltcp TCP runner/listener and
the reqwest client builder. These ran on the runtime task, so a panic
there would unwind into tokio and tear the JVM down. They are now
io::Error / Tun2SocksError values.
- Add a oneshot shutdown channel so stop() can signal the runner to drop
out of its main select{} rather than relying solely on the AtomicBool
busy-poll. Previous behaviour (poll the flag in the TUN reader) is
retained as a fallback.
- Guard the unsafe fcntl() calls: log on F_GETFL / F_SETFL failure
instead of silently writing back -1 | O_NONBLOCK.
- Reset TUN2SOCKS_RUNNING on every early-return path inside start() so a
failed start is retryable without restarting the process.
Behavioural change at the JNI seam: an invalid proxy URL now returns -1
from nativeStart synchronously instead of panicking inside the runtime
task after Kotlin has already moved on.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d_connect - Wrap both Java_..._nativeStart and Java_..._nativeStop bodies in std::panic::catch_unwind so a panic in Rust can never unwind across the FFI boundary (which is undefined behaviour). nativeStart returns -2 on caught panic, -1 on expected error, 0 on success. - Replace the panicking get_runtime() with try_get_runtime() that returns Tun2SocksError::Runtime on builder failure. - Drop the dead protected_connect() helper from protect.rs. It was marked #[allow(dead_code)], constructed the same socket twice via socket2 (the first socket and its protect() call were leaked) and was never wired in — outbound proxy traffic relies on addDisallowedApplication() on the VpnService instead. protect_fd() is kept (still allow(dead_code)) so the relay can opt in later if that policy changes. - Surface a JNI error if new_global_ref fails inside set_vpn_service rather than silently storing None. ABI: the #[no_mangle] symbol names Java_org_proxydroid_utils_Tun2SocksHelper_nativeStart and Java_org_proxydroid_utils_Tun2SocksHelper_nativeStop are unchanged; the C signatures match the existing Kotlin externals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Compose VPN-first UI never reads the PAC engine (com.btr.proxy),
the legacy ImageLoader / ImageLoaderFactory thread pool, or
InnerSocketBuilder. Profile.certificate was persisted but never
rendered or forwarded to the netstack. Utils.kt also kept Runtime.exec
("su") root helpers and an iptables probe that no caller invokes
under the VPN-only flow.
Removes:
- app/src/main/java/com/btr/proxy/** (PAC engine, 10 files, ~675 LOC)
- InnerSocketBuilder.kt
- utils/ImageLoader.kt + ImageLoaderFactory.kt
- Profile.certificate field (storage + JSON round-trip)
- Utils.runRootCommand / checkRoot / runScript / copyAssets /
getHasRedirectSupport / getIptablesPath / getShellPath /
isRoot / setRoot
Profile.kt: tightens nullability with .orEmpty(), drops mutable
`certificate` field, and removes the redundant try/catch around
toIntOrNull for port parsing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ProxyDroidVpnService now owns a SupervisorJob+Dispatchers.IO scope; the worker thread that built the tun device and started the Rust tun2socks is launched into that scope and cancelled in onDestroy. The `vpnInterface!!` deref is replaced with a local non-null after .establish(). ConnectivityBroadcastReceiver swaps Handler(Looper.getMainLooper()) + handler.post for BroadcastReceiver.goAsync() backed by a process-wide IO CoroutineScope. The handler body was split out and tightened with early-exits. Utils now exposes `working` and `connecting` as StateFlow<Boolean> in addition to the legacy @JvmStatic getters. MainViewModel collects those flows in viewModelScope, so the Compose connection card updates reactively instead of polling once per second. The polling loop in ProxyDroid.onCreate is gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DomainValidator + utils/RegexValidator have no inbound references under the VPN-first Compose UI. The legacy AppManager.getProxyedApps companion method was superseded by the local Compose loadApps() in the same file; the ProxyedApp data class only existed to serve that dead method. Drops: - DomainValidator.kt - utils/RegexValidator.kt - ProxyedApp.kt - AppManager.getProxyedApps companion (plus the lone `username!!`) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2e75bbf to
308c81b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Multi-agent refactor of ProxyDroid. Orchestrated by a 5-agent team (1 inventory + 4 parallel workers), one merge commit per worker on top of master. Verified locally with
./gradlew :app:assembleDebug+scripts/run_local_emulator_tests.sh→ 8/8 e2e tests green.Headline numbers
174 files changed, +1,308 / −55,632. ~33% Kotlin LOC reduction; entire libevent + redsocks C subtrees gone.
What changed, by worker
1. Dead-code purge (
refactor/dead-code-purge, 3 commits)app/src/main/cpp/libevent/(85 files, 45,459 LOC) andapp/src/main/cpp/redsocks/(34 files, ~7,000 LOC) — both obsoleted by the VPN-first redesign.ExecJNI shim (cpp/exec/,Exec.kt) and the dead Kotlin helpers inUtils.kt(checkRoot,runRootCommand,runScript,isRoot, etc.).drawable-{hdpi,ldpi,mdpi,xhdpi}-v{9,11}/), unusedbutton_*.xmlselectors.cpp/CMakeLists.txtto a single placeholder target so AGP'sexternalNativeBuildstill configures.2. Kotlin / Compose modernization (
refactor/kotlin-modernize, 3 commits)com/btr/proxy/**(PAC engine),InnerSocketBuilder,ImageLoader[Factory],DomainValidator/RegexValidator,ProxyedApp.Thread { }.start()/Handler(Looper.getMainLooper())/ a 1-second polling loop with coroutines + StateFlow throughoutProxyDroidVpnService,ConnectivityBroadcastReceiver,MainViewModel,ProxyDroid.Profile.kt(dropped deadcertificate; safe-call nullability), shrankUtils.ktto 68 LOC of pure VPN-state plumbing.3. Rust crate cleanup (
refactor/rust-cleanup, 3 commits)cargo fmt+cargo clippy --target aarch64-linux-android -- -D warningsclean (0/0).thiserror-basedTun2SocksError;expect()/Stringerrors replaced with structured returns.catch_unwind— panics no longer propagate across FFI (UB).nativeStartnow returns-2on panic,-1on expected failure,0on success. Kotlin caller already checksrc != 0, so ABI is compatible.oneshotchannel (replaces busy-polledAtomicBool).fcntlcalls (check for-1), dropped deadprotected_connect.4. Build / tooling (
refactor/build-tooling, 5 commits)gradle/libs.versions.tomlas the single source of dep versions; regroupedapp/build.gradledependencies (AndroidX / Compose / Kotlin / test).gradle.properties, kept the AGP-8.1.2 +android.suppressUnsupportedCompileSdk=36block (deliberate — bumping AGP retriggers the rust-android-gradle 0.9.6mergeJniLibFolderslandmine)..github/workflows/android-build.yml: cache catalog, drop deadCreate dummy google-services.jsonstep, addrefactor/**branch filter.README.mdrewritten for VPN-first / Compose / Rust netstack-smoltcp reality (was still claiming redsocks + iptables).privacy_policy.md: removed stale AdMob / Firebase references.Items requested for human eyeball
android-build.yml: confirm no downstream CI consumer expected the dummygoogle-services.json. Verified clean of GMS plugins/runtimes by the agent.privacy_policy.md: confirm wording for Play listing.Utils.copyAssets/Utils.getDataPath— they were unreferenced and have been dropped.Verification
./gradlew :app:assembleDebug— green (after one-timeapp/.cxxcache wipe because the deletedcpp/execwas cached).scripts/run_local_emulator_tests.shon AVDmeow_api35(arm64-v8a, API 35): 8/8 tests green in 0.3s of test time.🤖 Generated with Claude Code