Skip to content

Commit 8562b83

Browse files
committed
Merge branch 'fix/1916-system-bridge-autostart' into develop
2 parents 16880f3 + 1c72f8b commit 8562b83

25 files changed

Lines changed: 558 additions & 326 deletions

File tree

base/src/main/java/io/github/sds100/keymapper/base/BaseKeyMapperApp.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,6 @@ abstract class BaseKeyMapperApp : MultiDexApplication() {
105105
when (intent.action) {
106106
Intent.ACTION_SHUTDOWN -> {
107107
Timber.i("Clean shutdown")
108-
settingsRepository.set(Keys.isCleanShutdown, true)
109-
110-
// Block until the value is persisted.
111-
runBlocking {
112-
settingsRepository.get(Keys.isCleanShutdown).first { it == true }
113-
}
114108
}
115109
}
116110
}

base/src/main/java/io/github/sds100/keymapper/base/promode/ProModeScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,8 @@ private fun LoadedContent(
432432

433433
SwitchPreferenceCompose(
434434
modifier = Modifier.padding(horizontal = 8.dp),
435-
title = stringResource(R.string.title_pref_pro_mode_auto_start_at_boot),
436-
text = stringResource(R.string.summary_pref_pro_mode_auto_start_at_boot),
435+
title = stringResource(R.string.title_pref_pro_mode_auto_start),
436+
text = stringResource(R.string.summary_pref_pro_mode_auto_start),
437437
icon = Icons.Rounded.RestartAlt,
438438
isChecked = autoStartAtBoot,
439439
onCheckedChange = onAutoStartAtBootToggled,

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

Lines changed: 96 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import kotlinx.coroutines.flow.flowOf
4646
import kotlinx.coroutines.flow.map
4747
import kotlinx.coroutines.launch
4848
import kotlinx.coroutines.withTimeout
49+
import kotlinx.coroutines.withTimeoutOrNull
4950
import timber.log.Timber
5051

5152
/**
@@ -78,103 +79,105 @@ class SystemBridgeAutoStarter @Inject constructor(
7879
@SuppressLint("NewApi")
7980
@OptIn(ExperimentalCoroutinesApi::class)
8081
private val autoStartTypeFlow: Flow<AutoStartType?> =
81-
suAdapter.isRootGranted.flatMapLatest { isRooted ->
82-
if (isRooted) {
83-
flowOf(AutoStartType.ROOT)
84-
} else {
85-
val useShizukuFlow =
86-
combine(
87-
shizukuAdapter.isStarted,
88-
permissionAdapter.isGrantedFlow(Permission.SHIZUKU),
89-
) { isStarted, isGranted ->
90-
isStarted && isGranted
91-
}
92-
93-
useShizukuFlow.flatMapLatest { useShizuku ->
94-
if (useShizuku) {
95-
flowOf(AutoStartType.SHIZUKU)
96-
} else if (buildConfig.sdkInt >= Build.VERSION_CODES.R) {
97-
val isAdbAutoStartAllowed = combine(
98-
permissionAdapter.isGrantedFlow(Permission.WRITE_SECURE_SETTINGS),
99-
networkAdapter.isWifiConnected,
100-
) { isWriteSecureSettingsGranted, isWifiConnected ->
101-
isWriteSecureSettingsGranted &&
102-
isWifiConnected &&
103-
setupController.isAdbPaired()
82+
suAdapter.isRootGranted
83+
.filterNotNull()
84+
.flatMapLatest { isRooted ->
85+
if (isRooted) {
86+
flowOf(AutoStartType.ROOT)
87+
} else {
88+
val useShizukuFlow =
89+
combine(
90+
shizukuAdapter.isStarted,
91+
permissionAdapter.isGrantedFlow(Permission.SHIZUKU),
92+
) { isStarted, isGranted ->
93+
isStarted && isGranted
10494
}
10595

106-
isAdbAutoStartAllowed.distinctUntilChanged()
107-
.map { isAdbAutoStartAllowed ->
108-
if (isAdbAutoStartAllowed) AutoStartType.ADB else null
109-
}.filterNotNull()
110-
} else {
111-
flowOf(null)
96+
useShizukuFlow.flatMapLatest { useShizuku ->
97+
if (useShizuku) {
98+
flowOf(AutoStartType.SHIZUKU)
99+
} else if (buildConfig.sdkInt >= Build.VERSION_CODES.R) {
100+
val isAdbAutoStartAllowed = combine(
101+
permissionAdapter.isGrantedFlow(Permission.WRITE_SECURE_SETTINGS),
102+
networkAdapter.isWifiConnected,
103+
) { isWriteSecureSettingsGranted, isWifiConnected ->
104+
isWriteSecureSettingsGranted &&
105+
isWifiConnected &&
106+
setupController.isAdbPaired()
107+
}
108+
109+
isAdbAutoStartAllowed.distinctUntilChanged()
110+
.map { isAdbAutoStartAllowed ->
111+
if (isAdbAutoStartAllowed) {
112+
AutoStartType.ADB
113+
} else {
114+
null
115+
}
116+
}.filterNotNull()
117+
} else {
118+
flowOf(null)
119+
}
112120
}
113121
}
114122
}
115-
}
116123

117124
/**
118125
* This emits values when the system bridge needs restarting after it being killed.
119126
*/
120127
@OptIn(ExperimentalCoroutinesApi::class)
121-
private val restartFlow: Flow<AutoStartType?> =
128+
private val autoStartFlow: Flow<AutoStartType?> =
122129
connectionManager.connectionState.flatMapLatest { connectionState ->
130+
123131
// Do not autostart if it is connected or it was killed from the user
124132
if (connectionState !is SystemBridgeConnectionState.Disconnected ||
125-
connectionState.isExpected
133+
connectionState.isStoppedByUser ||
134+
!getIsUsedBefore() ||
135+
getIsStoppedByUser() ||
136+
isSystemBridgeEmergencyKilled() ||
137+
!isAutoStartEnabled()
126138
) {
127139
flowOf(null)
128-
} else {
140+
} else if (diedAfterAutostart(connectionState.time)) {
129141
// Do not autostart if the system bridge was killed shortly after.
130142
// This prevents infinite loops happening.
131-
if (lastAutoStartTime != null &&
132-
connectionState.time - lastAutoStartTime!! < 30000
133-
) {
134-
Timber.w(
135-
"Not auto starting the system bridge because it was last auto started less than 30 secs ago",
136-
)
137-
showSystemBridgeKilledNotification(
138-
getString(R.string.system_bridge_died_notification_not_restarting_text),
139-
)
140-
flowOf(null)
141-
} else {
142-
autoStartTypeFlow
143-
}
143+
Timber.w(
144+
"Not auto starting the system bridge because it was last auto started less than 5 mins ago",
145+
)
146+
showSystemBridgeKilledNotification(
147+
getString(R.string.system_bridge_died_notification_not_restarting_text),
148+
)
149+
flowOf(null)
150+
} else {
151+
autoStartTypeFlow
144152
}
145153
}
146154

147-
private var lastAutoStartTime: Long? = null
148-
149155
/**
150156
* This must only be called once in the application lifecycle
151157
*/
152-
@OptIn(FlowPreview::class)
158+
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
153159
fun init() {
154160
coroutineScope.launch {
155-
// The Key Mapper process may not necessarily be started on boot due to the
156-
// on boot receiver so assume if it is started within a minute of boot that
157-
// it should be auto started.
158-
val isBoot = clock.elapsedRealtime() <= 300_000
159-
160161
Timber.i(
161-
"SystemBridgeAutoStarter init: isBoot=$isBoot",
162+
"SystemBridgeAutoStarter init: time since boot=${clock.elapsedRealtime() / 1000} seconds",
162163
)
163164

164-
if (isBoot) {
165-
handleAutoStartOnBoot()
166-
} else if (BuildConfig.DEBUG && connectionManager.isConnected()) {
165+
// Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
166+
// before deciding whether to start it.
167+
delay(5000)
168+
169+
if (BuildConfig.DEBUG && connectionManager.isConnected()) {
167170
// This is useful when developing and need to restart the system bridge
168171
// after making changes to it.
169172
Timber.w("Restarting system bridge on debug build.")
170173

171174
connectionManager.restartSystemBridge()
172-
} else {
173-
handleAutoStartFromPreVersion4()
175+
delay(5000)
174176
}
175177

176-
// Only start collecting the restart flow after potentially auto starting it for the first time.
177-
restartFlow
178+
handleAutoStartFromPreVersion4()
179+
180+
autoStartFlow
178181
.distinctUntilChanged() // Must come before the filterNotNull
179182
.filterNotNull()
180183
.collectLatest { type ->
@@ -183,48 +186,15 @@ class SystemBridgeAutoStarter @Inject constructor(
183186
}
184187
}
185188

186-
private suspend fun handleAutoStartOnBoot() {
187-
// Do not autostart if the device was force rebooted. This may be a sign that PRO mode
188-
// was broken and the user was trying to reset it.
189-
val isCleanShutdown = preferences.get(Keys.isCleanShutdown).map { it ?: false }.first()
190-
191-
Timber.i(
192-
"SystemBridgeAutoStarter init: isCleanShutdown=$isCleanShutdown",
193-
)
194-
195-
// Reset the value after reading it.
196-
preferences.set(Keys.isCleanShutdown, false)
197-
198-
val isBootAutoStartEnabled = preferences.get(Keys.isProModeAutoStartBootEnabled)
199-
.map { it ?: PreferenceDefaults.PRO_MODE_AUTOSTART_BOOT }
200-
.first()
201-
202-
// Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
203-
// before starting it.
204-
delay(5000)
205-
206-
val connectionState = connectionManager.connectionState.value
207-
208-
if (isCleanShutdown &&
209-
isBootAutoStartEnabled &&
210-
connectionState !is SystemBridgeConnectionState.Connected
211-
) {
212-
// Wait a minute so the device can connect to a WiFi network, or a Shizuku
213-
// client can automatically connect on boot.
214-
Timber.i("Waiting 60 seconds before auto starting system bridge")
215-
delay(60000)
216-
val autoStartType = autoStartTypeFlow.first()
217-
218-
if (autoStartType != null) {
219-
autoStart(autoStartType)
220-
}
221-
}
222-
}
223-
224189
private suspend fun handleAutoStartFromPreVersion4() {
225-
val isFirstTime = preferences.get(Keys.handledRootToProModeUpgrade).first() == null
190+
@Suppress("DEPRECATION")
191+
val upgradedFromPreVersion4 = preferences.get(Keys.hasRootPermissionLegacy).first() != null
192+
193+
val isRooted: Boolean = withTimeoutOrNull(1000) {
194+
suAdapter.isRootGranted.filterNotNull().first()
195+
} ?: false
226196

227-
if (isFirstTime && suAdapter.isRootGranted.value) {
197+
if (upgradedFromPreVersion4 && isRooted) {
228198
Timber.i(
229199
"Auto starting system bridge because upgraded from pre version 4.0 and was rooted",
230200
)
@@ -235,19 +205,12 @@ class SystemBridgeAutoStarter @Inject constructor(
235205
}
236206

237207
private suspend fun autoStart(type: AutoStartType) {
238-
if (isSystemBridgeEmergencyKilled()) {
239-
Timber.w(
240-
"Not auto starting the system bridge because it was emergency killed by the user",
241-
)
242-
return
243-
}
244-
245208
if (connectionManager.isConnected()) {
246209
Timber.i("Not auto starting with $type because already connected.")
247210
return
248211
}
249212

250-
lastAutoStartTime = clock.elapsedRealtime()
213+
preferences.set(Keys.systemBridgeLastAutoStartTime, clock.elapsedRealtime())
251214

252215
when (type) {
253216
AutoStartType.ADB -> {
@@ -294,10 +257,33 @@ class SystemBridgeAutoStarter @Inject constructor(
294257
}
295258
}
296259

260+
private suspend fun getIsUsedBefore(): Boolean {
261+
return preferences.get(Keys.isSystemBridgeUsed).first() ?: false
262+
}
263+
264+
private suspend fun getIsStoppedByUser(): Boolean {
265+
return preferences.get(Keys.isSystemBridgeStoppedByUser).first() ?: false
266+
}
267+
297268
private suspend fun isSystemBridgeEmergencyKilled(): Boolean {
298269
return preferences.get(Keys.isSystemBridgeEmergencyKilled).first() == true
299270
}
300271

272+
/**
273+
* Whether the system bridge died less than 5 minutes after the previous time it was
274+
* auto started.
275+
*/
276+
private suspend fun diedAfterAutostart(disconnectionTime: Long): Boolean {
277+
val lastAutoStartTime = preferences.get(Keys.systemBridgeLastAutoStartTime).first()
278+
return lastAutoStartTime != null && disconnectionTime - lastAutoStartTime < (5 * 60_000)
279+
}
280+
281+
private suspend fun isAutoStartEnabled(): Boolean {
282+
return preferences.get(Keys.isSystemBridgeKeepAliveEnabled)
283+
.map { it ?: PreferenceDefaults.PRO_MODE_KEEP_ALIVE }
284+
.first()
285+
}
286+
301287
private fun showSystemBridgeKilledNotification(text: String) {
302288
val model = NotificationModel(
303289
id = ID_SYSTEM_BRIDGE_STATUS,

base/src/main/java/io/github/sds100/keymapper/base/promode/SystemBridgeSetupUseCase.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
120120
}
121121
}
122122

123-
override val isRootGranted: Flow<Boolean> = suAdapter.isRootGranted
123+
override val isRootGranted: Flow<Boolean> = suAdapter.isRootGranted.map { it ?: false }
124124

125125
override val shizukuSetupState: Flow<ShizukuSetupState> = combine(
126126
shizukuAdapter.isInstalled,
@@ -148,6 +148,10 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
148148
}
149149

150150
override fun stopSystemBridge() {
151+
// Save that they've stopped the system bridge so when the app process launches again
152+
// it will set the isStoppedByUser to true.
153+
preferences.set(Keys.isSystemBridgeStoppedByUser, true)
154+
151155
systemBridgeConnectionManager.stopSystemBridge()
152156
}
153157

@@ -174,16 +178,19 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
174178

175179
override fun startSystemBridgeWithRoot() {
176180
preferences.set(Keys.isSystemBridgeEmergencyKilled, false)
181+
preferences.set(Keys.isSystemBridgeStoppedByUser, false)
177182
systemBridgeSetupController.startWithRoot()
178183
}
179184

180185
override fun startSystemBridgeWithShizuku() {
181186
preferences.set(Keys.isSystemBridgeEmergencyKilled, false)
187+
preferences.set(Keys.isSystemBridgeStoppedByUser, false)
182188
systemBridgeSetupController.startWithShizuku()
183189
}
184190

185191
override suspend fun startSystemBridgeWithAdb() {
186192
preferences.set(Keys.isSystemBridgeEmergencyKilled, false)
193+
preferences.set(Keys.isSystemBridgeStoppedByUser, false)
187194
if (isAdbAutoStartAllowed.first()) {
188195
systemBridgeSetupController.autoStartWithAdb()
189196
} else {
@@ -200,12 +207,12 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
200207
}
201208

202209
override val isAutoStartBootEnabled: Flow<Boolean> =
203-
preferences.get(Keys.isProModeAutoStartBootEnabled)
204-
.map { it ?: PreferenceDefaults.PRO_MODE_AUTOSTART_BOOT }
210+
preferences.get(Keys.isSystemBridgeKeepAliveEnabled)
211+
.map { it ?: PreferenceDefaults.PRO_MODE_KEEP_ALIVE }
205212

206213
override fun toggleAutoStartBoot() {
207-
preferences.update(Keys.isProModeAutoStartBootEnabled) {
208-
!(it ?: PreferenceDefaults.PRO_MODE_AUTOSTART_BOOT)
214+
preferences.update(Keys.isSystemBridgeKeepAliveEnabled) {
215+
!(it ?: PreferenceDefaults.PRO_MODE_KEEP_ALIVE)
209216
}
210217
}
211218

base/src/main/java/io/github/sds100/keymapper/base/settings/ConfigSettingsUseCase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class ConfigSettingsUseCaseImpl @Inject constructor(
5757
Theme.entries.single { it.value == value.toInt() }
5858
}
5959

60-
override val isRootGranted: Flow<Boolean> = suAdapter.isRootGranted
60+
override val isRootGranted: Flow<Boolean> = suAdapter.isRootGranted.map { it ?: false }
6161

6262
override val isWriteSecureSettingsGranted: Flow<Boolean> = channelFlow {
6363
send(permissionAdapter.isGranted(Permission.WRITE_SECURE_SETTINGS))

base/src/main/res/values-pt/strings.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,8 +1300,8 @@ Deixe em branco se alguma rede Wi-Fi precisar ser correspondida.</string>
13001300
<string name="pro_mode_settings_unavailable_text">Essas configurações ficarão indisponíveis até que você confirme o aviso.</string>
13011301
<string name="pro_mode_service_started">O serviço do modo PRO está em execução</string>
13021302
<string name="pro_mode_stop_service_button">Parar</string>
1303-
<string name="title_pref_pro_mode_auto_start_at_boot">Iniciar automaticamente na inicialização</string>
1304-
<string name="summary_pref_pro_mode_auto_start_at_boot">O Modo PRO será iniciado sempre que você ligar ou reiniciar seu dispositivo</string>
1303+
<string name="title_pref_pro_mode_auto_start">Iniciar automaticamente na inicialização</string>
1304+
<string name="summary_pref_pro_mode_auto_start">O Modo PRO será iniciado sempre que você ligar ou reiniciar seu dispositivo</string>
13051305
<string name="pro_mode_emergency_tip_title">Dica de emergência</string>
13061306
<string name="pro_mode_emergency_tip_text">Se o botão liga/desliga parar de funcionar, mantenha-o pressionado por 10 segundos e solte para desativar o Modo PRO.</string>
13071307
<string name="pro_mode_setup_wizard_title">Assistente de configuração</string>

base/src/main/res/values-tr/strings.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,8 +1302,8 @@ Kaydedilmiş ses dosyalarını ayarlardan silebilirsiniz.</string>
13021302
<string name="pro_mode_settings_unavailable_text">Bu ayarlar, uyarıyı kabul edene kadar kullanılamaz.</string>
13031303
<string name="pro_mode_service_started">PRO modu hizmeti çalışıyor</string>
13041304
<string name="pro_mode_stop_service_button">Durdur</string>
1305-
<string name="title_pref_pro_mode_auto_start_at_boot">Açılışta otomatik olarak başlat</string>
1306-
<string name="summary_pref_pro_mode_auto_start_at_boot">Cihazınızı her açtığınızda veya yeniden başlattığınızda PRO Modu kendini başlatacaktır</string>
1305+
<string name="title_pref_pro_mode_auto_start">Açılışta otomatik olarak başlat</string>
1306+
<string name="summary_pref_pro_mode_auto_start">Cihazınızı her açtığınızda veya yeniden başlattığınızda PRO Modu kendini başlatacaktır</string>
13071307
<string name="pro_mode_emergency_tip_title">Acil durum ipucu</string>
13081308
<string name="pro_mode_emergency_tip_text">Güç düğmeniz çalışmazsa, PRO Modu\'nu devre dışı bırakmak için güç düğmesini 10 saniye basılı tutun ve bırakın.</string>
13091309
<string name="pro_mode_setup_wizard_title">Kurulum sihirbazı</string>

0 commit comments

Comments
 (0)