Skip to content

Commit 9493130

Browse files
committed
feat: Show dialog if Expert mode fails to start after 60 seconds instead of waiting indefinitely
1 parent 3621b8f commit 9493130

8 files changed

Lines changed: 95 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- #2025 add report bug button to home screen menu.
99
- #2027 Make the key map sorting feature easier to understand.
1010
- #2016 Show a warning when repeating a key code action less than 20 ms with expert mode triggers.
11+
- Show dialog if Expert mode fails to start after 60 seconds instead of waiting indefinitely.
1112

1213
## Fixed
1314

base/src/main/java/io/github/sds100/keymapper/base/actions/ConfigActionsViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.github.sds100.keymapper.common.utils.mapData
3030
import io.github.sds100.keymapper.common.utils.onFailure
3131
import io.github.sds100.keymapper.system.SystemError
3232
import io.github.sds100.keymapper.system.permissions.Permission
33+
import javax.inject.Inject
3334
import kotlinx.coroutines.flow.MutableStateFlow
3435
import kotlinx.coroutines.flow.SharingStarted
3536
import kotlinx.coroutines.flow.StateFlow
@@ -42,7 +43,6 @@ import kotlinx.coroutines.flow.map
4243
import kotlinx.coroutines.flow.stateIn
4344
import kotlinx.coroutines.flow.update
4445
import kotlinx.coroutines.launch
45-
import javax.inject.Inject
4646

4747
@HiltViewModel
4848
class ConfigActionsViewModel @Inject constructor(

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import androidx.compose.material.icons.rounded.Numbers
3838
import androidx.compose.material.icons.rounded.RestartAlt
3939
import androidx.compose.material.icons.rounded.Usb
4040
import androidx.compose.material.icons.rounded.WarningAmber
41+
import androidx.compose.material3.AlertDialog
4142
import androidx.compose.material3.BottomAppBar
4243
import androidx.compose.material3.ButtonDefaults
4344
import androidx.compose.material3.CardDefaults
@@ -87,6 +88,12 @@ fun ExpertModeScreen(modifier: Modifier = Modifier, viewModel: ExpertModeViewMod
8788
val expertModeWarningState by viewModel.warningState.collectAsStateWithLifecycle()
8889
val expertModeState by viewModel.state.collectAsStateWithLifecycle()
8990

91+
if (viewModel.showStartErrorDialog) {
92+
SystemBridgeStartErrorDialog(
93+
onDismissRequest = viewModel::dismissStartErrorDialog,
94+
)
95+
}
96+
9097
ExpertModeScreen(
9198
modifier = modifier,
9299
onBackClick = viewModel::onBackClick,
@@ -1074,6 +1081,39 @@ private fun PreviewUsbDebuggingSecuritySettingsCard() {
10741081
}
10751082
}
10761083

1084+
@Composable
1085+
private fun SystemBridgeStartErrorDialog(
1086+
modifier: Modifier = Modifier,
1087+
onDismissRequest: () -> Unit,
1088+
) {
1089+
AlertDialog(
1090+
modifier = modifier,
1091+
onDismissRequest = onDismissRequest,
1092+
title = {
1093+
Text(stringResource(R.string.expert_mode_start_error_dialog_title))
1094+
},
1095+
text = {
1096+
Text(
1097+
stringResource(R.string.expert_mode_start_error_dialog_message),
1098+
style = MaterialTheme.typography.bodyMedium,
1099+
)
1100+
},
1101+
confirmButton = {
1102+
TextButton(onClick = onDismissRequest) {
1103+
Text(stringResource(R.string.pos_ok))
1104+
}
1105+
},
1106+
)
1107+
}
1108+
1109+
@Preview
1110+
@Composable
1111+
private fun PreviewSystemBridgeStartErrorDialog() {
1112+
KeyMapperTheme {
1113+
SystemBridgeStartErrorDialog(onDismissRequest = {})
1114+
}
1115+
}
1116+
10771117
@Preview
10781118
@Composable
10791119
private fun PreviewShellStartCard() {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ class ExpertModeViewModel @Inject constructor(
7070
}
7171
}.stateIn(viewModelScope, SharingStarted.Eagerly, State.Loading)
7272

73+
var showStartErrorDialog by mutableStateOf(false)
74+
private set
75+
76+
init {
77+
viewModelScope.launch {
78+
useCase.showStartError.collect {
79+
showStartErrorDialog = true
80+
}
81+
}
82+
}
83+
84+
fun dismissStartErrorDialog() {
85+
showStartErrorDialog = false
86+
}
87+
7388
var showInfoCard by mutableStateOf(!useCase.isInfoDismissed())
7489
private set
7590

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ class SystemBridgeSetupUseCaseImpl @Inject constructor(
107107
override val isSystemBridgeStarting: Flow<Boolean> =
108108
systemBridgeSetupController.isStarting
109109

110+
override val showStartError: Flow<Unit> =
111+
systemBridgeSetupController.showStartError
112+
110113
override val isNotificationPermissionGranted: Flow<Boolean> =
111114
permissionAdapter.isGrantedFlow(Permission.POST_NOTIFICATIONS)
112115

@@ -325,6 +328,7 @@ interface SystemBridgeSetupUseCase {
325328

326329
val isSystemBridgeConnected: Flow<Boolean>
327330
val isSystemBridgeStarting: Flow<Boolean>
331+
val showStartError: Flow<Unit>
328332
val nextSetupStep: Flow<SystemBridgeSetupStep>
329333

330334
val isRootGranted: Flow<Boolean>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1693,6 +1693,8 @@
16931693
<string name="expert_mode_shell_start_get_command">Get command</string>
16941694
<string name="expert_mode_shell_start_retry">Retry</string>
16951695
<string name="expert_mode_shell_start_error">Failed to get command. Please try again.</string>
1696+
<string name="expert_mode_start_error_dialog_title">Failed to start Expert Mode</string>
1697+
<string name="expert_mode_start_error_dialog_message">The system bridge could not be started. Please try again or contact the developer at contact@keymapper.app if the problem persists.</string>
16961698
<string name="expert_mode_shell_start_clipboard_label">System Bridge Start Command</string>
16971699
<string name="expert_mode_shell_start_copy_content_description">Copy command to clipboard</string>
16981700

sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/manager/SystemBridgeConnectionManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class SystemBridgeConnectionManagerImpl @Inject constructor(
9999
try {
100100
starter.refreshStarterScript()
101101
} catch (e: Exception) {
102-
Timber.e("Failed to refresh system bridge starter script", e)
102+
Timber.e("Failed to refresh system bridge starter script. $e")
103103
}
104104
}
105105
}
@@ -282,7 +282,7 @@ class SystemBridgeConnectionManagerImpl @Inject constructor(
282282
starter.startWithRoot()
283283
}
284284

285-
override fun startWithShizuku() {
285+
override suspend fun startWithShizuku() {
286286
starter.startWithShizuku()
287287
}
288288

@@ -304,7 +304,7 @@ interface SystemBridgeConnectionManager {
304304

305305
suspend fun startWithRoot()
306306

307-
fun startWithShizuku()
307+
suspend fun startWithShizuku()
308308

309309
suspend fun startWithAdb()
310310

sysbridge/src/main/java/io/github/sds100/keymapper/sysbridge/service/SystemBridgeSetupController.kt

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.github.sds100.keymapper.sysbridge.adb.AdbManager
2323
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
2424
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
2525
import io.github.sds100.keymapper.sysbridge.manager.awaitConnected
26+
import io.github.sds100.keymapper.sysbridge.service.SystemBridgeSetupControllerImpl.Companion.START_TIMEOUT_MS
2627
import javax.inject.Inject
2728
import javax.inject.Singleton
2829
import kotlinx.coroutines.CoroutineScope
@@ -54,6 +55,7 @@ class SystemBridgeSetupControllerImpl @Inject constructor(
5455
companion object {
5556
private const val DEVELOPER_OPTIONS_SETTING = "development_settings_enabled"
5657
private const val ADB_WIRELESS_SETTING = "adb_wifi_enabled"
58+
private const val START_TIMEOUT_MS = 60_000L
5759
}
5860

5961
private val activityManager: ActivityManager by lazy { ctx.getSystemService()!! }
@@ -62,6 +64,9 @@ class SystemBridgeSetupControllerImpl @Inject constructor(
6264
override val isStarting: StateFlow<Boolean> = _isStarting
6365
private var startJob: Job? = null
6466

67+
private val _showStartError: MutableSharedFlow<Unit> = MutableSharedFlow()
68+
override val showStartError: Flow<Unit> = _showStartError
69+
6570
override val isDeveloperOptionsEnabled: MutableStateFlow<Boolean> =
6671
MutableStateFlow(getDeveloperOptionsEnabled())
6772

@@ -107,48 +112,30 @@ class SystemBridgeSetupControllerImpl @Inject constructor(
107112
}
108113

109114
override fun startWithRoot() {
110-
if (startJob?.isActive == true) {
111-
Timber.i("System Bridge is already starting")
112-
return
113-
}
114-
115-
startJob = coroutineScope.launch {
116-
_isStarting.value = true
117-
try {
118-
connectionManager.startWithRoot()
119-
// Wait for the service to bind and start system bridge
120-
withTimeoutOrNull(10000L) {
121-
connectionManager.awaitConnected()
122-
}
123-
} finally {
124-
_isStarting.value = false
125-
}
115+
launchStartJob {
116+
connectionManager.startWithRoot()
126117
}
127118
}
128119

129120
override fun startWithShizuku() {
130-
if (startJob?.isActive == true) {
131-
Timber.i("System Bridge is already starting")
132-
return
133-
}
134-
135-
startJob = coroutineScope.launch {
136-
_isStarting.value = true
137-
try {
138-
connectionManager.startWithShizuku()
139-
140-
// Wait for the service to bind and start system bridge
141-
withTimeoutOrNull(10000L) {
142-
connectionManager.awaitConnected()
143-
}
144-
} finally {
145-
_isStarting.value = false
146-
}
121+
launchStartJob {
122+
connectionManager.startWithShizuku()
147123
}
148124
}
149125

150126
@RequiresApi(Build.VERSION_CODES.R)
151127
override fun startWithAdb() {
128+
launchStartJob {
129+
connectionManager.startWithAdb()
130+
}
131+
}
132+
133+
/**
134+
* Launch a start job that runs [start], then waits up to [START_TIMEOUT_MS] for the
135+
* system bridge to connect. If it does not connect in time, an error is emitted
136+
* via [showStartError].
137+
*/
138+
private fun launchStartJob(start: suspend () -> Unit) {
152139
if (startJob?.isActive == true) {
153140
Timber.i("System Bridge is already starting")
154141
return
@@ -157,11 +144,17 @@ class SystemBridgeSetupControllerImpl @Inject constructor(
157144
startJob = coroutineScope.launch {
158145
_isStarting.value = true
159146
try {
160-
connectionManager.startWithAdb()
147+
start()
148+
161149
// Wait for the service to bind and start system bridge
162-
withTimeoutOrNull(10000L) {
150+
val connected = withTimeoutOrNull(START_TIMEOUT_MS) {
163151
connectionManager.awaitConnected()
164152
}
153+
154+
if (connected == null) {
155+
Timber.e("System Bridge failed to start within ${START_TIMEOUT_MS}ms")
156+
_showStartError.emit(Unit)
157+
}
165158
} finally {
166159
_isStarting.value = false
167160
}
@@ -422,6 +415,7 @@ class SystemBridgeSetupControllerImpl @Inject constructor(
422415
interface SystemBridgeSetupController {
423416
val setupAssistantStep: Flow<SystemBridgeSetupStep?>
424417
val isStarting: StateFlow<Boolean>
418+
val showStartError: Flow<Unit>
425419

426420
val isDeveloperOptionsEnabled: Flow<Boolean>
427421
fun enableDeveloperOptions()

0 commit comments

Comments
 (0)