Skip to content

Commit 881ae88

Browse files
Yuevil-raider
authored andcommitted
fix(expert mode): don't apply auto start cooldown after a reboot
The system bridge auto start has a 5 minute cooldown that prevents it from being auto started again if it was last auto started less than 5 minutes ago. This exists to avoid an infinite restart loop when the system bridge keeps being killed shortly after starting. The cooldown was based solely on the wall clock time of the last auto start (systemBridgeLastAutoStartTime), which persists across reboots. So if the user rebooted, used the device for 1-2 minutes and rebooted again, the system bridge was stopped by the reboot (a correct termination, not a crash) but the cooldown still fired. The user got the "expert mode stopped unexpectedly / not auto restarting because last auto started less than 5 minutes ago" notification and the service did not start. Store the device boot time (unix time - elapsed realtime) alongside the last auto start time, and skip the cooldown when the current boot time differs from the stored one, i.e. the device has rebooted since the last auto start. A crash loop within a single boot session still triggers the cooldown because the boot time does not change between kills. Fixes #2195
1 parent f35b9c0 commit 881ae88

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

base/src/main/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeAutoStarter.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ class SystemBridgeAutoStarter @Inject constructor(
284284
// Otherwise, it may not autostart on reboot if it started earlier than when it last auto
285285
// started relative to the last boot.
286286
preferences.set(Keys.systemBridgeLastAutoStartTime, clock.unixTimestamp())
287+
preferences.set(Keys.systemBridgeLastAutoStartBootTime, bootTimestamp())
287288

288289
when (type) {
289290
AutoStartType.ADB -> {
@@ -349,6 +350,8 @@ class SystemBridgeAutoStarter @Inject constructor(
349350
private suspend fun isWithinAutoStartCooldown(): Boolean {
350351
val lastAutoStartTime = preferences.get(Keys.systemBridgeLastAutoStartTime).first()
351352
val lastManualStartTime = preferences.get(Keys.systemBridgeLastManualStartTime).first()
353+
val lastAutoStartBootTime =
354+
preferences.get(Keys.systemBridgeLastAutoStartBootTime).first()
352355
val currentTime = clock.unixTimestamp()
353356

354357
if (lastAutoStartTime == null) {
@@ -362,10 +365,31 @@ class SystemBridgeAutoStarter @Inject constructor(
362365
return false
363366
}
364367

368+
// If the device has rebooted since the last auto start then the system bridge stopped
369+
// because of the reboot and not because it crashed. The cooldown only exists to prevent
370+
// an infinite restart loop within a single boot session, so ignore it after a reboot.
371+
// Otherwise rebooting twice within 5 minutes wrongly prevents the system bridge from
372+
// auto starting again.
373+
if (lastAutoStartBootTime != null &&
374+
bootTimestamp() - lastAutoStartBootTime > REBOOT_DETECTION_TOLERANCE_SEC
375+
) {
376+
return false
377+
}
378+
365379
return currentTime >= lastAutoStartTime &&
366380
currentTime - lastAutoStartTime < (5 * 60)
367381
}
368382

383+
/**
384+
* The approximate unix time in seconds at which the device last booted, calculated from the
385+
* current wall clock time minus the time elapsed since boot. This value is stable within a
386+
* boot session (aside from small clock adjustments) and jumps forward when the device
387+
* reboots, so it can be used to detect that a reboot has happened.
388+
*/
389+
private fun bootTimestamp(): Long {
390+
return clock.unixTimestamp() - (clock.elapsedRealtime() / 1000)
391+
}
392+
369393
private suspend fun isAutoStartEnabled(): Boolean {
370394
return preferences.get(Keys.isSystemBridgeKeepAliveEnabled)
371395
.map { it ?: PreferenceDefaults.EXPERT_MODE_KEEP_ALIVE }
@@ -451,4 +475,14 @@ class SystemBridgeAutoStarter @Inject constructor(
451475

452476
notificationAdapter.showNotification(model)
453477
}
478+
479+
companion object {
480+
/**
481+
* The boot timestamp is only accurate to within a few seconds because the wall clock and
482+
* elapsed realtime are not sampled at exactly the same instant and the clock may be
483+
* adjusted by the system. This tolerance avoids mistaking that jitter for a reboot while
484+
* still reliably detecting a real reboot, which shifts the boot timestamp far more.
485+
*/
486+
private const val REBOOT_DETECTION_TOLERANCE_SEC = 60L
487+
}
454488
}

base/src/test/java/io/github/sds100/keymapper/base/expertmode/SystemBridgeAutoStarterTest.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,4 +783,33 @@ class SystemBridgeAutoStarterTest {
783783
verify(mockConnectionManager, never()).startWithRoot()
784784
}
785785
}
786+
787+
@Test
788+
fun `auto start within 5 minutes of the last auto start if the device rebooted in between`() =
789+
runTest(testDispatcher) {
790+
fakePreferences.set(Keys.isSystemBridgeKeepAliveEnabled, true)
791+
fakePreferences.set(Keys.isSystemBridgeUsed, true)
792+
fakePreferences.set(Keys.handledUpgradeToExpertMode, true)
793+
794+
// The system bridge was last auto started only 2 minutes ago, which is within the
795+
// cooldown, but that was during a previous boot because the device has rebooted since.
796+
fakePreferences.set(
797+
Keys.systemBridgeLastAutoStartTime,
798+
testScopeClock.unixTimestamp() - 120,
799+
)
800+
fakePreferences.set(
801+
Keys.systemBridgeLastAutoStartBootTime,
802+
// Booted an hour before the current boot, i.e. the device has rebooted since.
803+
testScopeClock.unixTimestamp() - testScopeClock.elapsedRealtime() / 1000 - 3600,
804+
)
805+
isRootGrantedFlow.value = true
806+
807+
inOrder(mockConnectionManager) {
808+
systemBridgeAutoStarter.init()
809+
advanceTimeBy(6000)
810+
811+
// The cooldown is skipped because the device rebooted, so it auto starts.
812+
verify(mockConnectionManager).startWithRoot()
813+
}
814+
}
786815
}

data/src/main/java/io/github/sds100/keymapper/data/Keys.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ object Keys {
150150
longPreferencesKey("key_system_bridge_last_manual_start_time")
151151
val systemBridgeLastAutoStartTime = longPreferencesKey("key_system_bridge_last_auto_start_time")
152152

153+
/**
154+
* The unix time in seconds of the last device boot at which the system bridge was auto
155+
* started. Used to detect whether the device has rebooted since the last auto start so that
156+
* the auto start cooldown is not applied after a reboot.
157+
*/
158+
val systemBridgeLastAutoStartBootTime =
159+
longPreferencesKey("key_system_bridge_last_auto_start_boot_time")
160+
153161
val keyEventActionsUseSystemBridge =
154162
booleanPreferencesKey("key_key_event_actions_use_system_bridge")
155163

0 commit comments

Comments
 (0)