android: watchdog backs off whenever a live process owns recovery

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]>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-10 09:12:22 +07:00
1 parent 24e536b9b9
commit cf89cec20e
2 files changed
+46 -12

No files matched your search

@@ -150,6 +150,19 @@ public class WatchdogReceiver extends BroadcastReceiver
return Action.RESTART;
}
/**
* Whether a check counts towards the steady-state backoff of a live
* process: connected, retrying/connecting, or held back by the ISP
* whitelist. In all of these the in-process machinery owns recovery and
* the alarm can only guard against the process dying - firing every
* minute (and waking Doze every 10 minutes) for hours of failed retries
* or whitelist blocking only burns battery.
*/
static boolean isAliveSteady(Action action, boolean processAlive, boolean parked)
{
return action == Action.NONE && processAlive && !parked;
}
@Override
public void onReceive(Context context, Intent intent)
{
@@ -164,27 +177,24 @@ public class WatchdogReceiver extends BroadcastReceiver
return;
}
reschedule(context);
if (WhiteListManager.getInstance(context).shouldBlockReconnect())
{ /* the ISP whitelist blocks the server; the in-process retry
* machinery parks itself in that case, so should we */
DiagLog.i(TAG, "whitelist blocks the server, not restarting");
return;
}
/* the ISP whitelist blocks the server; the in-process retry
* machinery parks itself in that case, so should we */
boolean whitelistBlocks = WhiteListManager.getInstance(context).shouldBlockReconnect();
VpnStateService service = VpnStateService.getLiveInstance();
boolean processAlive = service != null;
boolean serviceTracking = processAlive && service.getState() != VpnStateService.State.DISABLED;
boolean wifiPresent = isWifiPresent(context);
boolean autoProfileConfigured = AutoConnectManager.getInstance(context).getCurrentConfig().getProfileUuid() != null;
Action action = decideAction(enabled, uuid != null, autoExpected, false, serviceTracking,
Action action = decideAction(enabled, uuid != null, autoExpected, whitelistBlocks, serviceTracking,
processAlive, wifiPresent, autoProfileConfigured);
boolean connected = serviceTracking && service.getState() == VpnStateService.State.CONNECTED;
boolean parked = uuid == null && autoExpected && processAlive && !serviceTracking && action == Action.NONE;
boolean alive = isAliveSteady(action, processAlive, parked);
if (parked)
{
sParkedChecks++;
sConnectedChecks = 0;
}
else if (connected && action == Action.NONE)
else if (alive)
{
sConnectedChecks++;
sParkedChecks = 0;
@@ -195,8 +205,8 @@ public class WatchdogReceiver extends BroadcastReceiver
sConnectedChecks = 0;
}
boolean parkedSteady = parked && sParkedChecks >= PARKED_BACKOFF_AFTER;
boolean connectedSteady = connected && sConnectedChecks >= CONNECTED_BACKOFF_AFTER;
String cadence = parkedSteady ? "parked" : connectedSteady ? "connected" : null;
boolean connectedSteady = alive && sConnectedChecks >= CONNECTED_BACKOFF_AFTER;
String cadence = parkedSteady ? "parked" : connectedSteady ? "alive" : null;
if (cadence != null)
{ /* steady state: log the transition once, then stay silent - a
* per-fire line only floods diag.log and burns flash I/O */
@@ -210,7 +220,7 @@ public class WatchdogReceiver extends BroadcastReceiver
{
DiagLog.i(TAG, "check: uuid=" + (uuid != null) + " autoExpected=" + autoExpected +
" processAlive=" + processAlive + " tracking=" + serviceTracking +
" wifi=" + wifiPresent + " -> " + action);
" whitelist=" + whitelistBlocks + " wifi=" + wifiPresent + " -> " + action);
}
sLastCadence = cadence;
switch (action)
@@ -125,4 +125,28 @@ class WatchdogReceiverTest
assertThat(decide(expectedProfileSet = true, autoExpected = true, processAlive = false, wifiPresent = false))
.isEqualTo(Action.RESTART)
}
@Test
fun liveProcessRetryingOrWhitelistedBacksOff()
{
/* retrying/connecting: the service tracks the connection */
val retrying = decide(expectedProfileSet = true, serviceTracking = true, processAlive = true)
assertThat(retrying).isEqualTo(Action.NONE)
assertThat(WatchdogReceiver.isAliveSteady(retrying, true, false)).isTrue()
/* whitelist blocks the server while the process is alive */
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = true)
assertThat(WatchdogReceiver.isAliveSteady(blocked, true, false)).isTrue()
}
@Test
fun deadProcessOrRestartNeverBacksOff()
{
val restart = decide(expectedProfileSet = true, processAlive = false)
assertThat(WatchdogReceiver.isAliveSteady(restart, false, false)).isFalse()
/* whitelist blocks, but no live process: keep the fast cadence */
val blocked = decide(expectedProfileSet = true, whitelistBlocks = true, processAlive = false)
assertThat(WatchdogReceiver.isAliveSteady(blocked, false, false)).isFalse()
/* parked sessions use their own backoff */
assertThat(WatchdogReceiver.isAliveSteady(Action.NONE, true, true)).isFalse()
}
}