Skip to content

Commit 0f04175

Browse files
committed
#1939 show a notification when expert mode fails to start due to being disconnected from wifi
1 parent ec88303 commit 0f04175

4 files changed

Lines changed: 131 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
## Added
66

7-
- #1970 dynamically build the key code list so key codes in new Android releases are automatically included.
7+
- #1970 dynamically build the key code list so key codes in new Android releases are automatically
8+
included.
9+
- #1939 show notification when Expert Mode fails to start due to be being disconnected from WiFi.
10+
811
## Fixed
912

1013
- Bugs with expert mode auto starting time.
@@ -19,9 +22,11 @@
1922

2023
## Bug fixes
2124

22-
- #1968 Device controls action no longer works on Android 16+ so it has been disabled on new Android versions.
25+
- #1968 Device controls action no longer works on Android 16+ so it has been disabled on new Android
26+
versions.
2327
- #1967 Still start system bridge if granting WRITE_SECURE_SETTINGS fails.
24-
- #1965 Better system bridge support on Xiaomi devices and ask to enable "USB debugging security settings" in developer options.
28+
- #1965 Better system bridge support on Xiaomi devices and ask to enable "USB debugging security
29+
settings" in developer options.
2530

2631
## [4.0.0 Beta 5](https://github.com/sds100/KeyMapper/releases/tag/v4.0.0-beta.05)
2732

@@ -30,6 +35,7 @@
3035
Happy new year!
3136

3237
## Added
38+
3339
- #1947 show tip to use expert mode where the old option for screen off remapping used to be
3440

3541
## Bug fixes

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

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -69,54 +69,61 @@ class SystemBridgeAutoStarter @Inject constructor(
6969
private val notificationAdapter: NotificationAdapter,
7070
private val resourceProvider: ResourceProvider,
7171
) : ResourceProvider by resourceProvider {
72-
enum class AutoStartType {
72+
73+
private enum class AutoStartType {
7374
ADB,
7475
SHIZUKU,
7576
ROOT,
7677
}
7778

79+
private sealed class AutoStartEligibility {
80+
data class Eligible(val type: AutoStartType) : AutoStartEligibility()
81+
sealed class NotEligible : AutoStartEligibility() {
82+
data object WiFiDisconnected : NotEligible()
83+
data object AutoStartCooldown : NotEligible()
84+
data object Other : NotEligible()
85+
}
86+
}
87+
7888
// Use flatMapLatest so that any calls to ADB are only done if strictly necessary.
7989
@SuppressLint("NewApi")
8090
@OptIn(ExperimentalCoroutinesApi::class)
81-
private val autoStartTypeFlow: Flow<AutoStartType?> =
91+
private val autoStartTypeFlow: Flow<AutoStartEligibility> =
8292
suAdapter.isRootGranted
8393
.filterNotNull()
8494
.flatMapLatest { isRooted ->
8595
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
94-
}
96+
return@flatMapLatest flowOf(AutoStartEligibility.Eligible(AutoStartType.ROOT))
97+
}
9598

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-
}
99+
val useShizukuFlow =
100+
combine(
101+
shizukuAdapter.isStarted,
102+
permissionAdapter.isGrantedFlow(Permission.SHIZUKU),
103+
) { isStarted, isGranted ->
104+
isStarted && isGranted
105+
}
108106

109-
isAdbAutoStartAllowed.distinctUntilChanged()
110-
.map { isAdbAutoStartAllowed ->
111-
if (isAdbAutoStartAllowed) {
112-
AutoStartType.ADB
113-
} else {
114-
null
115-
}
116-
}.filterNotNull()
117-
} else {
118-
flowOf(null)
107+
useShizukuFlow.flatMapLatest { useShizuku ->
108+
if (useShizuku) {
109+
flowOf(AutoStartEligibility.Eligible(AutoStartType.SHIZUKU))
110+
} else if (buildConfig.sdkInt >= Build.VERSION_CODES.R) {
111+
combine(
112+
permissionAdapter.isGrantedFlow(Permission.WRITE_SECURE_SETTINGS),
113+
networkAdapter.isWifiConnected,
114+
) { isWriteSecureSettingsGranted, isWifiConnected ->
115+
if (!isWifiConnected) {
116+
AutoStartEligibility.NotEligible.WiFiDisconnected
117+
} else if (!isWriteSecureSettingsGranted) {
118+
AutoStartEligibility.NotEligible.Other
119+
} else if (!setupController.isAdbPaired()) {
120+
AutoStartEligibility.NotEligible.Other
121+
} else {
122+
AutoStartEligibility.Eligible(AutoStartType.ADB)
123+
}
119124
}
125+
} else {
126+
flowOf(AutoStartEligibility.NotEligible.Other)
120127
}
121128
}
122129
}
@@ -125,7 +132,7 @@ class SystemBridgeAutoStarter @Inject constructor(
125132
* This emits values when the system bridge needs restarting after it being killed.
126133
*/
127134
@OptIn(ExperimentalCoroutinesApi::class)
128-
private val autoStartFlow: Flow<AutoStartType?> =
135+
private val autoStartFlow: Flow<AutoStartEligibility> =
129136
connectionManager.connectionState.flatMapLatest { connectionState ->
130137
// Do not autostart if it is connected or it was killed from the user
131138
if (connectionState !is SystemBridgeConnectionState.Disconnected ||
@@ -135,17 +142,9 @@ class SystemBridgeAutoStarter @Inject constructor(
135142
isSystemBridgeEmergencyKilled() ||
136143
!isAutoStartEnabled()
137144
) {
138-
flowOf(null)
145+
flowOf(AutoStartEligibility.NotEligible.Other)
139146
} else if (isWithinAutoStartCooldown()) {
140-
// Do not autostart if the system bridge was killed shortly after.
141-
// This prevents infinite loops happening.
142-
Timber.w(
143-
"Not auto starting the system bridge because it was last auto started less than 5 mins ago",
144-
)
145-
showSystemBridgeKilledNotification(
146-
getString(R.string.system_bridge_died_notification_not_restarting_text),
147-
)
148-
flowOf(null)
147+
flowOf(AutoStartEligibility.NotEligible.AutoStartCooldown)
149148
} else {
150149
autoStartTypeFlow
151150
}
@@ -178,10 +177,28 @@ class SystemBridgeAutoStarter @Inject constructor(
178177

179178
autoStartFlow
180179
.distinctUntilChanged() // Must come before the filterNotNull
181-
.filterNotNull()
182-
.collectLatest { type ->
183-
autoStart(type)
184-
}
180+
.collectLatest(::processAutoStartEligibility)
181+
}
182+
}
183+
184+
private suspend fun processAutoStartEligibility(eligibility: AutoStartEligibility) {
185+
when (eligibility) {
186+
is AutoStartEligibility.Eligible -> autoStart(eligibility.type)
187+
188+
AutoStartEligibility.NotEligible.AutoStartCooldown -> {
189+
// Do not autostart if the system bridge was killed shortly after.
190+
// This prevents infinite loops happening.
191+
Timber.w(
192+
"Not auto starting the system bridge because it was last auto started less than 5 mins ago",
193+
)
194+
showSystemBridgeKilledNotification(
195+
getString(R.string.system_bridge_died_notification_not_restarting_text),
196+
)
197+
}
198+
199+
AutoStartEligibility.NotEligible.WiFiDisconnected -> showWiFiDisconnectedNotification()
200+
201+
AutoStartEligibility.NotEligible.Other -> {}
185202
}
186203
}
187204

@@ -353,7 +370,27 @@ class SystemBridgeAutoStarter @Inject constructor(
353370
priority = NotificationCompat.PRIORITY_MAX,
354371
onGoing = true,
355372
showIndeterminateProgress = true,
356-
showOnLockscreen = false,
373+
showOnLockscreen = true,
374+
)
375+
376+
notificationAdapter.showNotification(model)
377+
}
378+
379+
private fun showWiFiDisconnectedNotification() {
380+
val model = NotificationModel(
381+
id = ID_SYSTEM_BRIDGE_STATUS,
382+
title = getString(
383+
R.string.system_bridge_wifi_disconnected_notification_title,
384+
),
385+
text = getString(R.string.system_bridge_wifi_disconnected_notification_text),
386+
onClickAction = KMNotificationAction.Activity.MainActivity(
387+
BaseMainActivity.ACTION_START_SYSTEM_BRIDGE,
388+
),
389+
channel = CHANNEL_SETUP_ASSISTANT,
390+
icon = R.drawable.offline_bolt_24px,
391+
priority = NotificationCompat.PRIORITY_MAX,
392+
onGoing = false,
393+
showOnLockscreen = true,
357394
)
358395

359396
notificationAdapter.showNotification(model)
@@ -371,7 +408,7 @@ class SystemBridgeAutoStarter @Inject constructor(
371408
channel = CHANNEL_SETUP_ASSISTANT,
372409
icon = R.drawable.offline_bolt_24px,
373410
onGoing = false,
374-
showOnLockscreen = false,
411+
showOnLockscreen = true,
375412
autoCancel = true,
376413
priority = NotificationCompat.PRIORITY_MAX,
377414
onClickAction = KMNotificationAction.Activity.MainActivity(

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,9 @@
17761776
<string name="system_bridge_died_notification_restarting_text">Automatically restarting…</string>
17771777
<string name="system_bridge_died_notification_not_restarting_text">Not auto restarting because last auto started less than 5 minutes ago. If you\'re not killing the service report the issue to the developer.</string>
17781778

1779+
<string name="system_bridge_wifi_disconnected_notification_title">Starting Expert mode failed</string>
1780+
<string name="system_bridge_wifi_disconnected_notification_text">Your phone must be connected to a WiFi network</string>
1781+
17791782
<!-- Trigger Discover Screen -->
17801783
<string name="trigger_discover_screen_title">Discover</string>
17811784
<string name="trigger_discover_screen_subtitle">What do you want to remap?</string>

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package io.github.sds100.keymapper.base.expertmode
22

3+
import androidx.core.app.NotificationCompat
4+
import io.github.sds100.keymapper.base.BaseMainActivity
35
import io.github.sds100.keymapper.base.R
46
import io.github.sds100.keymapper.base.repositories.FakePreferenceRepository
7+
import io.github.sds100.keymapper.base.system.notifications.NotificationController
58
import io.github.sds100.keymapper.base.utils.TestBuildConfigProvider
69
import io.github.sds100.keymapper.base.utils.TestScopeClock
710
import io.github.sds100.keymapper.base.utils.ui.ResourceProvider
11+
import io.github.sds100.keymapper.common.notifications.KMNotificationAction
812
import io.github.sds100.keymapper.data.Keys
913
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
1014
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
@@ -464,6 +468,34 @@ class SystemBridgeAutoStarterTest {
464468
}
465469
}
466470

471+
@Test
472+
fun `show wifi disconnected notification when auto starting`() = runTest(testDispatcher) {
473+
fakePreferences.set(Keys.isSystemBridgeKeepAliveEnabled, true)
474+
fakePreferences.set(Keys.isSystemBridgeUsed, true)
475+
isWifiConnectedFlow.value = false
476+
writeSecureSettingsGrantedFlow.value = true
477+
478+
inOrder(mockNotificationAdapter) {
479+
systemBridgeAutoStarter.init()
480+
advanceTimeBy(10000)
481+
482+
val expectedModel = NotificationModel(
483+
id = NotificationController.ID_SYSTEM_BRIDGE_STATUS,
484+
channel = NotificationController.CHANNEL_SETUP_ASSISTANT,
485+
title = "test_string",
486+
text = "test_string",
487+
icon = R.drawable.offline_bolt_24px,
488+
onClickAction = KMNotificationAction.Activity.MainActivity(
489+
action = BaseMainActivity.ACTION_START_SYSTEM_BRIDGE,
490+
),
491+
priority = NotificationCompat.PRIORITY_MAX,
492+
showOnLockscreen = true,
493+
onGoing = false,
494+
)
495+
verify(mockNotificationAdapter).showNotification(expectedModel)
496+
}
497+
}
498+
467499
@Test
468500
fun `show failed notification when connection times out`() = runTest(testDispatcher) {
469501
isRootGrantedFlow.value = true

0 commit comments

Comments
 (0)