Skip to content

Add the Android app - #1

Merged
andrewchen5678 merged 3 commits into
mainfrom
android-app
Aug 21, 2026
Merged

andrewchen5678 merged 3 commits into
mainfrom
android-app

Conversation

@andrewchen5678

Copy link
Copy Markdown
Contributor

Summary

  • Kotlin/Jetpack Compose client for ezvpn: EzvpnVpnService running the Rust core (libezvpn.so) inside a VpnService, TunnelsManager, encrypted profile/auth-key stores, and the EzvpnNative JNI binding.
  • Pure-Kotlin tunnelcore module (CIDR math, route subtraction for the underlay bypass, profile validation, TunnelPlan, split-DNS rules) with JVM unit tests.
  • Gradle downloads the pinned libezvpn-android.zip from the ezvpn release (v0.0.41, sha256 in gradle.properties); EZVPN_LOCAL_JNILIBS=1 builds against the sibling checkout.
  • CI: unit tests, debug APK, and a check that the release pin is filled in.

Test plan

  • ./gradlew :tunnelcore:test :app:testDebugUnitTest :app:assembleDebug (clean pinned download path)
  • Smoke-tested on a Nexus 7 (armeabi-v7a, Android 11): key generate/rename/delete, connect to a local ezvpn server, split routes + split DNS, path query, disconnect; the pinned v0.0.41 build launches
  • CI green on this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01WZxQCX8Kwr4yZV6R96TaYP

Andrew Chen and others added 3 commits August 21, 2026 08:57
A native Kotlin/Jetpack Compose client for ezvpn. The app, EzvpnVpnService,
TunnelsManager, the AndroidKeyStore-encrypted secret and profile stores, the
shared auth-key manager, and EzvpnNative (the JNI binding to libezvpn.so,
whose symbols fix the class at dev.flexaccess.ezvpn.EzvpnNative) live in
:app; the pure-Kotlin :tunnelcore module holds the CIDR math (including the
bypass-by-subtraction route plan, since VpnService has no excludeRoute before
API 33), the profile model and editor validation, the VpnService.Builder plan,
and the split-DNS rules, all unit-tested on the JVM.

Split DNS (match domains) is implemented by pointing the VPN's DNS at the
core's in-tunnel forwarder address, routed as a host route, with the
underlying network's resolvers and protect()ed fallback sockets handed to the
core at connect time. Network changes disconnect; split-tunnel prefixes that
overlap the current Wi-Fi/Ethernet subnet are refused; always-on starts
connect the last-used profile.

libezvpn.so comes from the pinned ezvpn release zip (tag + sha256 in
gradle.properties, scripts/bump-jnilibs.sh) or, with EZVPN_LOCAL_JNILIBS=1,
from the sibling ../ezvpn/dist/android build; scripts/run-device.sh builds,
installs, launches, and tails logcat on the connected device.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZxQCX8Kwr4yZV6R96TaYP
CI fails without a valid ezvpn release pin instead of building an APK with
no native library; README lists the app unit tests; the jniLibs download
has timeouts.

Stores: AuthKeyStore no longer persists the pruned list at load, rename of
an unknown id is an error, and the mutators are synchronized; AuthKey
guards the JNI call; ProfileStore keeps a load-error state that refuses
writes and commits lastProfileId synchronously; TunnelsManager clears the
pending id from live state and lets a queued connect start when no service
exists; the service resolves ConnectivityManager once and checks it.

UI: consent pending id survives recreation; ConnPathSheet has loading and
error states; EzvpnRoot pops a missing profile from an effect; clipboard
null-checks the service; the relay token stays out of saved state and is
loaded off-main; key operations run on a background dispatcher; the list
screen uses string resources; the launcher foreground fits the adaptive
safe zone and the duplicate round icon is gone.

tunnelcore: IPv4 literals are built directly with octet range checks,
NetworkConfig.parse rejects malformed mtu/prefix_len6, routes are stored
in canonical form; tests cover the malformed fields. Scripts: portable
sha256/sed in bump-jnilibs.sh, emulator refusal in run-device.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZxQCX8Kwr4yZV6R96TaYP
Fills in the sha256 of the published libezvpn-android.zip so the default
(non-local) build and the CI pin check work; bumps versionCode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WZxQCX8Kwr4yZV6R96TaYP
@andrewchen5678
andrewchen5678 merged commit 173cd67 into main Aug 21, 2026
1 check passed
@andrewchen5678
andrewchen5678 deleted the android-app branch August 21, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant