- WITH_TUNNEL mode pings the server only once the external targets fail
(no ping fork + DNS lookup outside the tunnel on every healthy cycle;
an ICMP-blocking server no longer forces fast polls)
- initial battery-low state from the sticky ACTION_BATTERY_CHANGED
- non-state notifications no longer restart a pending cycle
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- same SHA-256 as the applied list: only bump the timestamp, no parse,
save or tunnel reconnect (it tore a healthy tunnel down daily)
- a failed download is not retried on every cold start (3 h backoff)
- the saved list is read and parsed off the main thread at startup
- switching the split off in system mode reconnects to drop the routes
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Retrying, connecting or whitelist-blocked sessions kept the 60 s alarm and
the 10 min Doze wakeup for hours although a live process makes the alarm
useless. They now share the connected backoff (5 min / 60 min).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After a silent pre-download the state stayed Downloading, so tapping the
"update ready" notification did nothing. It now returns to Available;
an install request without a known update (e.g. after a process restart)
re-checks and installs when found.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- SimpleDateFormat is now used under the lock; rotation counts bytes
- AutoConnectManager.stop() clears pendingEvaluate, whose reset runnable
it removes, so later connects are not silently swallowed
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- remove pending RETRY_MSG on reset/set: a Retry tap during a backoff
started a second countdown chain (double-speed ticks, two reconnects)
- notify listeners over a copy with per-listener error handling
- disconnect() tolerates the VPN service not running
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- DISCONNECT (plain startService) no longer promotes to the foreground,
and startForeground() refusals on Android 12+ are logged, not fatal
- drop the placeholder notification when nothing was running, so the
service does not stay in the foreground with a stale Disabled state
- onDestroy: shutdownNow/await the profile executor before closing the
database; profile loads catch errors instead of killing the process
- unbind by bind result, not by mService (binding leaked if destroyed
before onServiceConnected)
- the connection thread's crash guard can no longer throw itself
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tcpPing never closed its socket: every health/whitelist/home probe leaked
an fd until GC and the peer's later FIN/RST woke the radio again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
getRoutingStats/getDeadDirectTargets are polled from the UI once a second;
a concurrent disconnect frees the engine under charon_mutex, so reading it
without the lock was a use-after-free.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AGP 9 strict full-mode keep rules no longer keep no-arg constructors of
-keep'd classes, so WorkManager's reflectively created Room database
lost its constructor and the app crashed in InitializationProvider.
Restore AGP 8 keep-rule semantics and keep RoomDatabase constructors.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Built-in Kotlin (kotlin-android removed), Java 11 compile options (compose 1.12),
proguard-android-optimize.txt (the non-optimize file is rejected by AGP 9),
ExposedDropdownMenu import, minSdk 23 kept via tools:overrideLibrary for
material3 1.5 libs that declare minSdk 24. -PshortNdkCommands for deep worktree paths.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop unused navigation-compose and material3-window-size-class.
alpha19+/beta01 need AGP 9.1 + compileSdk 37, so alpha18 is pinned.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Direct destinations that stay unreachable past the cooldown are now
routed through the VPN tunnel until the network changes. The Home
availability card marks such hosts "via tunnel" and offers a one-tap
"Via VPN" action for the rest.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
underlyingNetworkLabel() returned the first non-VPN network from
allNetworks, whose order is unspecified - Android keeps the cellular
network agent alive in parallel with Wi-Fi, so on Wi-Fi the details
sheet could show "Сотовая сеть". Wi-Fi now wins when present (same
precedence as NetworkMonitor's trust decision); cellular is only
reported when no Wi-Fi network exists.
- ConnectionDetailsSheet: session traffic split "via VPN / direct (RU)"
from the native engine counters (userspace mode only; system-routes
mode shows no split - the engine never sees that traffic), public exit
IP + country via cloudflare cdn-cgi/trace through the tunnel, current
underlying network (Wi-Fi + SSID / cellular)
- ConnectionCard: one-glance "N% direct" caption when the RU split moves
traffic directly
- dead direct hosts surfaced in UI: native snapshot of the negative-cache
table via new get_dead_targets (mutex-guarded, sorted by failures) +
JNI getDeadDirectTargets; section on the Routing tab and a badge in the
Home availability card naming dst, failure count, remaining cooldown
and the owning app (from the 2.13.15 owner-attribution cache)
- HomeViewModel: split/exit/deadTargets as separate hot flows; exit
refreshed with the probe round (60s cache), dead targets every round
minSdk 23 flipped AGP's useLegacyPackaging default to false (uncompressed
.so, extractNativeLibs=false): the APK grew 12 -> 23 MB. Updates are
downloaded over cellular, so pin useLegacyPackaging = true; APK is back
to ~12 MB (2.14.0 re-published with the same versionCode, sha updated in
the manifest before any device fetched it).
- minSdkVersion 21 -> 23 (device is Android 17, no other users); jni
Application.mk: APP_PLATFORM android-23 + -DHAVE_SIGWAITINFO (the NDK
sysroot declares sigwaitinfo since API 23, conflicting with strongSwan's
fallback prototype; the system implementation is used now)
- replace the bottom NavigationBar with NavigationSuiteScaffold (bottom bar
on phones, navigation rail in landscape/on tablets); item colors keep the
brand palette via NavigationSuiteItemColors(bar/rail/drawer)
- HomeScreen applies statusBarsPadding itself (the adaptive scaffold no
longer pads the content area)
- predictive back was already enabled (android:enableOnBackInvokedCallback)