- Routing: site and direct-address rules (sites resolved to addresses,
refreshed on connect and every 30 min), package patterns can be
switched off; unified Rules screen with add sheet and empty state;
"How traffic goes" for a site, IP or app with reasons, reachability and
one-tap rules; Recent apps (usage access) with failing apps; new-app
"go direct?" suggestions in Events.
- Diagnostics screen (all-in-one check), "Fastest" server, pause until
morning / until the network changes.
- Design system of the mockup: Manrope font, mockup palette for light and
dark, ShxKit components (cards, rows, switch, buttons, chips, tabs,
fields, sheets), mockup line icons; Home, nav bar, Routing, Rules,
Recent, Events, Diagnostics and route check rebuilt on it; shared
components restyled so the remaining screens pick up the look.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- EventLog keeps what the app noticed and did by itself for 30 days on the
device (events.json); repeats of the same event within 30 minutes are
merged (x3), problems that go away are marked resolved.
- Recorded: connection errors and recovery, carrier whitelists on/off,
health-check reconnects, auto-connect starting/parking the tunnel, the
end of a pause, available updates.
- Problems are pushed on a new "Connection problems" channel, but only
while the app is in the background (Home shows them already).
- «Events» screen (InboxActivity): filters (all, need attention,
connection, routes, app), grouped by day, one action per event; opened
from a bell with an unread badge in the Home header.
- Amber warning colours in the status palette, used for whitelists, no
network and an unresponsive server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Connect tab gets the Shield-S header and, while connected, three quick
actions: pause, switch server, "site not loading?" (route check).
- Pause VPN for 5/15/30/60 min (PauseManager + PauseReceiver): disconnects
like a manual stop and reconnects on an alarm; auto-connect waits, any
connect request cancels the pause. Home shows a countdown, "turn on now"
and "+15 minutes".
- Connection errors become plain-language cards with one fixing action:
no internet (tracked via a non-VPN network callback), server not
responding (with the retry countdown and "other server"), server not
found, sign-in rejected, server not verified, generic failure.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- adaptive launcher icon as vectors: purple background, white shield with
the S cut out (even-odd), plus a monochrome layer for Android 13+ themed
icons; profile shortcuts use the same icon
- vector legacy icon for Android 7.x in mipmap-anydpi, old strongSwan PNGs
removed
- connected status bar icon is the Shield-S silhouette; connecting,
warning and disconnected states keep their outlined shields
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- HomeViewModel closes the profile database in finally blocks and relies
on the VPN_PROFILES_CHANGED broadcast instead of reloading twice
- READ_EXTERNAL_STORAGE only up to API 32; drop a dead settings flag
Co-Authored-By: Claude Opus 5.5 <[email protected]>
They can block for 10-30 s on a degraded network and delayed any profile
load (connect, retry, profile switch) queued behind them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- tile: query the configured profile once per listening session instead
of on every state notification
- widget: DISCONNECTING is shown as such, with a Disconnect button
- log viewer: bounded to 5000 lines, tails only while visible, picks up
a log created or restarted after opening, and no longer yanks the user
back to the bottom while they scroll up
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- computeExcludedPackages() (full PackageManager scan) runs on Default,
not Main.immediate, on config changes and package add/remove
- updateConfig() ignores no-op transforms and reconnects when routing is
switched off too (direct apps kept bypassing the tunnel until then)
- config is volatile and read after its version, so an old config can no
longer be cached under a new version
- Direct apps loads the app list once per entry, without GET_META_DATA
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- underlying-network bookkeeping is guarded by one lock and re-applied
after establish(), so a handover racing with it no longer leaves the
tunnel attributed to a lost network
- use the capabilities passed to onCapabilitiesChanged (no IPC per
signal-strength update); mCurrentProfile is volatile
- countdown-only notification re-posts are limited to one per 5 s
- the connected chronometer keeps its start time across re-posts
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- scheduled rechecks are forced: the 15/30 s steps were below FRESH_MS,
so the unforced recheck returned early and the backoff chain died
- signal-strength-only capability changes are no longer network events
(each one reset the backoff and launched a check)
- tunnel state transitions force a new round at most every 15 s
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- traffic (1 s) and probe (10 s) loops run only while the Home screen is
shown and the VPN is connected; no 1 s idle tick otherwise
- the refresh button no longer runs a second exit-IP trace
- the glow pulse is not composed at all while disconnected or with
animations off (it redrew a transparent circle every frame)
- RTT sparkline appends each probe result once (it duplicated stale ones)
- Routing dead-host poll is lifecycle-aware
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- drop the in-process 12 h loop: the VPN keeps the process alive, so it
checked and pre-downloaded on battery, defeating the worker constraint
- the worker now waits for the check and a silent download it starts
- metered is judged by the physical network: the VPN reports itself
unmetered, so pre-downloads ran over cellular while connected
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- 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)
Log-driven fixes from the 10-04 diag/charon analysis:
- direct_tcp: negative-cache window now escalates 45s x2 up to 5 min
(Telegram was opening ~3000 connects/h to each of two dead hosts,
waking the radio for every leaked 45s probe); skip the unconditional
fresh-socket retry on ECONNREFUSED (a refusal is deterministic on the
network the socket is bound to; roam-time sockets are already aborted
by reprotect)
- new dead-destination diag line (dst, fail count, cooldown) via JNI
callback when the window escalates or hits every 100th failure
- VpnStateService: double-start guard - a second connect() for the same
profile while a healthy attempt is in flight is ignored (four starters
raced through here within 20 s after the 2.13.14 self-update)
- NetworkMonitor/AutoConnectManager: flap detection (>=2 kind changes in
5 min stretches the auto-connect delay to 10 s; a Wi-Fi flap produced
a 7-second-lived session and a wasted IKE handshake)
- hardcode diag TAGs obfuscated by R8 ("a: update: up to date" ->
"UpdateManager: ..."), same for UpdateInstaller/VpnShortcutManager
Diag/charon logs from 2026-09-30 16:45 showed that after a Wi-Fi->cellular
roam every direct-TCP connect failed asynchronously with EHOSTUNREACH for
minutes, while the IKE socket worked and a Java socket explicitly bound to
the underlying network connected fine: a bare VpnService.protect() left
fresh sockets routed over the vanished network.
- CharonVpnService.protectDirect(fd): protect + Network.bindSocket to the
active non-VPN network (API 23+, best-effort, adoptFd/detachFd)
- charonservice_protect_direct_socket() JNI wrapper; relay and UDP flow
sockets use it at creation
- direct_tcp: one retry on a fresh socket when an async connect fails;
reprotect aborts still-connecting relays (RST) instead of letting the
kernel take ~90s to report the dead route; failures log dst=ip:port
- direct_udp: reprotect closes all flows; they re-open per packet with a
fresh, network-pinned socket
- TUN reader: EBADF after a reauth re-establish is no longer logged
Published as 2.13.10 (versionCode 147).
Replaces the fragment-based UI with a full Jetpack Compose app
(HOME/profiles/routing/settings/trusted networks in Compose, adaptive
brand theming, notification/vector drawables), adds the SHX release
pipeline (build-local.sh, UpdateManager self-update from the S3 bucket,
DiagLog diagnostics rotated into diag.log with crash capture) and the
watchdog/auto-connect/reconnect hardening series.
RU-GeoIP split now has two modes (Routing screen):
- OS routes (default): coarsened complement of the RU list installed
as system routes, bounded by the binder budget (2.12.x behavior)
- Userspace routing (beta): only the default routes point into the
TUN; every packet is classified natively against the exact RU list
(ring-buffer queue, fixed flow tables - allocation-free hot path)
and RU TCP/UDP leaves through protected sockets. TCP is terminated
by a vendored lwIP 2.2.0 (tun2socks model, err_t renamed to avoid
the strongSwan typedef clash, wildcard-port listener patch, asserts
into diag.log). IPv4 addresses are compared in Java's numeric byte
order and ranges sorted natively; direct flows re-protect on roam;
relay errors log errno. Route diagnostics block 12s after connect.
Also: SIM2-crash hardening, connectivity watchdog with wakeup alarm,
profile export, connectivity self-check screen, tests.
Published as 2.13.9 (versionCode 146).
- Added new entries to .gitignore to exclude Android build artifacts and keystore files.
- Updated build.gradle files to include Kotlin Gradle plugin dependencies.
- Enhanced gradle.properties with JVM arguments for better performance.
- Modified AndroidManifest.xml to include additional permissions for network and location access.
- Refactored package names in several classes to use a new namespace.
- Removed deprecated LogActivity, SelectedApplicationsActivity, SettingsActivity, and TrustedCertificatesActivity.
- Introduced a new scheduling mechanism in the Scheduler class to handle exact alarms on Android 13+.
- Updated MainActivity to integrate new UI components.
These changes improve the overall structure and functionality of the Android application.