Skip to content

Commit 0721bf0

Browse files
committed
#1778 feat: show trigger error if evdev device is disconnected, PRO mode is disabled or unsupported
1 parent ba05741 commit 0721bf0

File tree

8 files changed

+111
-40
lines changed

8 files changed

+111
-40
lines changed

base/src/main/java/io/github/sds100/keymapper/base/home/KeyMapListScreen.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ private fun getTriggerErrorMessage(error: TriggerError): String {
489489
TriggerError.FLOATING_BUTTON_DELETED -> stringResource(R.string.trigger_error_floating_button_deleted)
490490
TriggerError.FLOATING_BUTTONS_NOT_PURCHASED -> stringResource(R.string.trigger_error_floating_buttons_not_purchased)
491491
TriggerError.PURCHASE_VERIFICATION_FAILED -> stringResource(R.string.trigger_error_product_verification_failed)
492+
TriggerError.SYSTEM_BRIDGE_UNSUPPORTED -> stringResource(R.string.trigger_error_system_bridge_unsupported)
493+
TriggerError.SYSTEM_BRIDGE_DISCONNECTED -> stringResource(R.string.trigger_error_system_bridge_disconnected)
494+
TriggerError.EVDEV_DEVICE_NOT_FOUND -> stringResource(R.string.trigger_error_evdev_device_not_found)
492495
}
493496
}
494497

base/src/main/java/io/github/sds100/keymapper/base/input/EvdevHandleCache.kt

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,42 @@ import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
1111
import io.github.sds100.keymapper.system.devices.DevicesAdapter
1212
import kotlinx.coroutines.CoroutineScope
1313
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.flow.MutableStateFlow
15+
import kotlinx.coroutines.flow.SharingStarted
16+
import kotlinx.coroutines.flow.StateFlow
1417
import kotlinx.coroutines.flow.collect
1518
import kotlinx.coroutines.flow.combine
19+
import kotlinx.coroutines.flow.map
20+
import kotlinx.coroutines.flow.stateIn
1621
import kotlinx.coroutines.launch
1722
import kotlinx.coroutines.withContext
1823
import timber.log.Timber
19-
import java.util.concurrent.ConcurrentHashMap
24+
import javax.inject.Inject
25+
import javax.inject.Singleton
2026

2127
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
22-
class EvdevHandleCache(
28+
@Singleton
29+
class EvdevHandleCache @Inject constructor(
2330
private val coroutineScope: CoroutineScope,
2431
private val devicesAdapter: DevicesAdapter,
2532
private val systemBridgeConnectionManager: SystemBridgeConnectionManager,
2633
) {
27-
private val devicesByPath: MutableMap<String, EvdevDeviceHandle> = ConcurrentHashMap()
34+
private val devicesByPath: MutableStateFlow<Map<String, EvdevDeviceHandle>> =
35+
MutableStateFlow(emptyMap())
36+
37+
val devices: StateFlow<List<EvdevDeviceInfo>> =
38+
devicesByPath
39+
.map { pathMap ->
40+
pathMap.values.map { device ->
41+
EvdevDeviceInfo(
42+
name = device.name,
43+
bus = device.bus,
44+
vendor = device.vendor,
45+
product = device.product,
46+
)
47+
}
48+
}
49+
.stateIn(coroutineScope, SharingStarted.Eagerly, emptyList())
2850

2951
init {
3052
coroutineScope.launch {
@@ -33,31 +55,20 @@ class EvdevHandleCache(
3355
systemBridgeConnectionManager.connectionState,
3456
) { _, connectionState ->
3557
if (connectionState !is SystemBridgeConnectionState.Connected) {
36-
devicesByPath.clear()
58+
devicesByPath.value = emptyMap()
3759
} else {
3860
invalidate()
3961
}
4062
}.collect()
4163
}
4264
}
4365

44-
fun getDevices(): List<EvdevDeviceInfo> {
45-
return devicesByPath.values.map { device ->
46-
EvdevDeviceInfo(
47-
name = device.name,
48-
bus = device.bus,
49-
vendor = device.vendor,
50-
product = device.product,
51-
)
52-
}
53-
}
54-
5566
fun getByPath(path: String): EvdevDeviceHandle? {
56-
return devicesByPath[path]
67+
return devicesByPath.value[path]
5768
}
5869

5970
fun getByInfo(deviceInfo: EvdevDeviceInfo): EvdevDeviceHandle? {
60-
return devicesByPath.values.firstOrNull {
71+
return devicesByPath.value.values.firstOrNull {
6172
it.name == deviceInfo.name &&
6273
it.bus == deviceInfo.bus &&
6374
it.vendor == deviceInfo.vendor &&
@@ -73,7 +84,6 @@ class EvdevHandleCache(
7384
Timber.e("Failed to get evdev input devices from system bridge $error")
7485
}.valueIfFailure { emptyMap() }
7586

76-
devicesByPath.clear()
77-
devicesByPath.putAll(newDevices)
87+
devicesByPath.value = newDevices
7888
}
7989
}

base/src/main/java/io/github/sds100/keymapper/base/input/InputEventHub.kt

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import io.github.sds100.keymapper.data.repositories.PreferenceRepository
2020
import io.github.sds100.keymapper.sysbridge.IEvdevCallback
2121
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
2222
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
23-
import io.github.sds100.keymapper.system.devices.DevicesAdapter
2423
import io.github.sds100.keymapper.system.inputevents.KMEvdevEvent
2524
import io.github.sds100.keymapper.system.inputevents.KMGamePadEvent
2625
import io.github.sds100.keymapper.system.inputevents.KMInputEvent
@@ -50,7 +49,7 @@ class InputEventHubImpl @Inject constructor(
5049
private val systemBridgeConnManager: SystemBridgeConnectionManager,
5150
private val imeInputEventInjector: ImeInputEventInjector,
5251
private val preferenceRepository: PreferenceRepository,
53-
private val devicesAdapter: DevicesAdapter,
52+
private val evdevHandlesCache: EvdevHandleCache
5453
) : InputEventHub, IEvdevCallback.Stub() {
5554

5655
companion object {
@@ -62,13 +61,6 @@ class InputEventHubImpl @Inject constructor(
6261
// Event queue for processing key events asynchronously in order
6362
private val keyEventQueue = Channel<InjectKeyEventModel>(capacity = 100)
6463

65-
@RequiresApi(Constants.SYSTEM_BRIDGE_MIN_API)
66-
private val evdevHandlesCache: EvdevHandleCache = EvdevHandleCache(
67-
coroutineScope,
68-
devicesAdapter,
69-
systemBridgeConnManager,
70-
)
71-
7264
private val logInputEventsEnabled: StateFlow<Boolean> =
7365
preferenceRepository.get(Keys.log).map { isLogEnabled ->
7466
if (isLogEnabled == true) {
@@ -268,7 +260,7 @@ class InputEventHubImpl @Inject constructor(
268260
throw IllegalArgumentException("This client $clientId is not registered when trying to grab devices!")
269261
}
270262

271-
val devices = evdevHandlesCache.getDevices().toSet()
263+
val devices = evdevHandlesCache.devices.value.toSet()
272264
clients[clientId] = clients[clientId]!!.copy(grabbedEvdevDevices = devices)
273265

274266
invalidateGrabbedDevicesChannel.trySend(Unit)

base/src/main/java/io/github/sds100/keymapper/base/keymaps/DisplayKeyMapUseCase.kt

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package io.github.sds100.keymapper.base.keymaps
22

33
import android.graphics.drawable.Drawable
4+
import android.os.Build
45
import dagger.hilt.android.scopes.ViewModelScoped
56
import io.github.sds100.keymapper.base.actions.DisplayActionUseCase
67
import io.github.sds100.keymapper.base.actions.GetActionErrorUseCase
78
import io.github.sds100.keymapper.base.constraints.DisplayConstraintUseCase
89
import io.github.sds100.keymapper.base.constraints.GetConstraintErrorUseCase
10+
import io.github.sds100.keymapper.base.input.EvdevHandleCache
911
import io.github.sds100.keymapper.base.purchasing.ProductId
10-
import io.github.sds100.keymapper.base.purchasing.PurchasingError
12+
import io.github.sds100.keymapper.base.purchasing.PurchasingError.ProductNotPurchased
1113
import io.github.sds100.keymapper.base.purchasing.PurchasingManager
1214
import io.github.sds100.keymapper.base.system.inputmethod.KeyMapperImeHelper
1315
import io.github.sds100.keymapper.base.system.inputmethod.SwitchImeInterface
@@ -17,6 +19,7 @@ import io.github.sds100.keymapper.base.utils.navigation.NavDestination
1719
import io.github.sds100.keymapper.base.utils.navigation.NavigationProvider
1820
import io.github.sds100.keymapper.base.utils.navigation.navigate
1921
import io.github.sds100.keymapper.common.BuildConfigProvider
22+
import io.github.sds100.keymapper.common.models.EvdevDeviceInfo
2023
import io.github.sds100.keymapper.common.utils.KMError
2124
import io.github.sds100.keymapper.common.utils.KMResult
2225
import io.github.sds100.keymapper.common.utils.State
@@ -27,8 +30,11 @@ import io.github.sds100.keymapper.common.utils.then
2730
import io.github.sds100.keymapper.common.utils.valueIfFailure
2831
import io.github.sds100.keymapper.data.Keys
2932
import io.github.sds100.keymapper.data.repositories.PreferenceRepository
33+
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionManager
34+
import io.github.sds100.keymapper.sysbridge.manager.SystemBridgeConnectionState
3035
import io.github.sds100.keymapper.sysbridge.utils.SystemBridgeError
31-
import io.github.sds100.keymapper.system.SystemError
36+
import io.github.sds100.keymapper.system.SystemError.ImeDisabled
37+
import io.github.sds100.keymapper.system.SystemError.PermissionDenied
3238
import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter
3339
import io.github.sds100.keymapper.system.apps.PackageManagerAdapter
3440
import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter
@@ -42,7 +48,9 @@ import kotlinx.coroutines.flow.callbackFlow
4248
import kotlinx.coroutines.flow.combine
4349
import kotlinx.coroutines.flow.filterIsInstance
4450
import kotlinx.coroutines.flow.first
51+
import kotlinx.coroutines.flow.flowOf
4552
import kotlinx.coroutines.flow.map
53+
import kotlinx.coroutines.flow.merge
4654
import kotlinx.coroutines.flow.onStart
4755
import kotlinx.coroutines.withTimeout
4856
import javax.inject.Inject
@@ -61,6 +69,8 @@ class DisplayKeyMapUseCaseImpl @Inject constructor(
6169
private val getConstraintErrorUseCase: GetConstraintErrorUseCase,
6270
private val buildConfigProvider: BuildConfigProvider,
6371
private val navigationProvider: NavigationProvider,
72+
private val systemBridgeConnectionManager: SystemBridgeConnectionManager,
73+
private val evdevHandleCache: EvdevHandleCache
6474
) : DisplayKeyMapUseCase,
6575
GetActionErrorUseCase by getActionErrorUseCase,
6676
GetConstraintErrorUseCase by getConstraintErrorUseCase {
@@ -94,22 +104,42 @@ class DisplayKeyMapUseCaseImpl @Inject constructor(
94104
purchasingManager.purchases.collect(this::send)
95105
}
96106

107+
private val systemBridgeConnectionState: Flow<SystemBridgeConnectionState?> =
108+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
109+
systemBridgeConnectionManager.connectionState
110+
} else {
111+
flowOf(null)
112+
}
113+
114+
private val evdevDevices: Flow<List<EvdevDeviceInfo>?> =
115+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
116+
evdevHandleCache.devices
117+
} else {
118+
flowOf(null)
119+
}
120+
97121
/**
98122
* Cache the data required for checking errors to reduce the latency of repeatedly checking
99123
* the errors.
100124
*/
101125
override val triggerErrorSnapshot: Flow<TriggerErrorSnapshot> = combine(
102-
permissionAdapter.onPermissionsUpdate.onStart { emit(Unit) },
126+
merge(
127+
permissionAdapter.onPermissionsUpdate.onStart { emit(Unit) },
128+
inputMethodAdapter.chosenIme
129+
),
103130
purchasesFlow,
104-
inputMethodAdapter.chosenIme,
105131
showDpadImeSetupError,
106-
) { _, purchases, _, showDpadImeSetupError ->
132+
systemBridgeConnectionState,
133+
evdevDevices
134+
) { _, purchases, showDpadImeSetupError, systemBridgeConnectionState, evdevDevices ->
107135
TriggerErrorSnapshot(
108136
isKeyMapperImeChosen = keyMapperImeHelper.isCompatibleImeChosen(),
109137
isDndAccessGranted = permissionAdapter.isGranted(Permission.ACCESS_NOTIFICATION_POLICY),
110138
isRootGranted = permissionAdapter.isGranted(Permission.ROOT),
111139
purchases = purchases.dataOrNull() ?: Success(emptySet()),
112140
showDpadImeSetupError = showDpadImeSetupError,
141+
isSystemBridgeConnected = systemBridgeConnectionState is SystemBridgeConnectionState.Connected,
142+
evdevDevices = evdevDevices
113143
)
114144
}
115145

@@ -126,24 +156,25 @@ class DisplayKeyMapUseCaseImpl @Inject constructor(
126156

127157
override suspend fun fixTriggerError(error: TriggerError) {
128158
when (error) {
129-
TriggerError.DND_ACCESS_DENIED -> fixError(SystemError.PermissionDenied(Permission.ACCESS_NOTIFICATION_POLICY))
159+
TriggerError.DND_ACCESS_DENIED -> fixError(PermissionDenied(Permission.ACCESS_NOTIFICATION_POLICY))
130160

131161
TriggerError.CANT_DETECT_IN_PHONE_CALL -> fixError(KMError.CantDetectKeyEventsInPhoneCall)
132162
TriggerError.ASSISTANT_TRIGGER_NOT_PURCHASED -> fixError(
133-
PurchasingError.ProductNotPurchased(
163+
ProductNotPurchased(
134164
ProductId.ASSISTANT_TRIGGER,
135165
),
136166
)
137167

138168
TriggerError.DPAD_IME_NOT_SELECTED -> fixError(KMError.DpadTriggerImeNotSelected)
139-
TriggerError.FLOATING_BUTTON_DELETED -> {}
140169
TriggerError.FLOATING_BUTTONS_NOT_PURCHASED -> fixError(
141-
PurchasingError.ProductNotPurchased(
170+
ProductNotPurchased(
142171
ProductId.FLOATING_BUTTONS,
143172
),
144173
)
145174

146175
TriggerError.PURCHASE_VERIFICATION_FAILED -> purchasingManager.refresh()
176+
TriggerError.SYSTEM_BRIDGE_DISCONNECTED -> fixError(SystemBridgeError.Disconnected)
177+
TriggerError.EVDEV_DEVICE_NOT_FOUND, TriggerError.FLOATING_BUTTON_DELETED, TriggerError.SYSTEM_BRIDGE_UNSUPPORTED -> {}
147178
}
148179
}
149180

@@ -166,8 +197,8 @@ class DisplayKeyMapUseCaseImpl @Inject constructor(
166197
}
167198

168199
KMError.NoCompatibleImeEnabled -> keyMapperImeHelper.enableCompatibleInputMethods()
169-
is SystemError.ImeDisabled -> switchImeInterface.enableIme(error.ime.id)
170-
is SystemError.PermissionDenied -> permissionAdapter.request(error.permission)
200+
is ImeDisabled -> switchImeInterface.enableIme(error.ime.id)
201+
is PermissionDenied -> permissionAdapter.request(error.permission)
171202
is KMError.ShizukuNotStarted -> packageManagerAdapter.openApp(ShizukuUtils.SHIZUKU_PACKAGE)
172203
is KMError.CantDetectKeyEventsInPhoneCall -> {
173204
if (!keyMapperImeHelper.isCompatibleImeEnabled()) {

base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerError.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ enum class TriggerError(val isFixable: Boolean) {
1818
FLOATING_BUTTONS_NOT_PURCHASED(isFixable = true),
1919

2020
PURCHASE_VERIFICATION_FAILED(isFixable = true),
21+
22+
SYSTEM_BRIDGE_UNSUPPORTED(isFixable = false),
23+
24+
SYSTEM_BRIDGE_DISCONNECTED(isFixable = true),
25+
26+
EVDEV_DEVICE_NOT_FOUND(isFixable = false)
2127
}

base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerErrorSnapshot.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import io.github.sds100.keymapper.base.keymaps.KeyMap
55
import io.github.sds100.keymapper.base.keymaps.requiresImeKeyEventForwardingInPhoneCall
66
import io.github.sds100.keymapper.base.purchasing.ProductId
77
import io.github.sds100.keymapper.base.purchasing.PurchasingError
8+
import io.github.sds100.keymapper.common.models.EvdevDeviceInfo
89
import io.github.sds100.keymapper.common.utils.KMResult
910
import io.github.sds100.keymapper.common.utils.onFailure
1011
import io.github.sds100.keymapper.common.utils.onSuccess
@@ -20,6 +21,14 @@ data class TriggerErrorSnapshot(
2021
val isRootGranted: Boolean,
2122
val purchases: KMResult<Set<ProductId>>,
2223
val showDpadImeSetupError: Boolean,
24+
/**
25+
* Can be null if the sdk version is not high enough.
26+
*/
27+
val isSystemBridgeConnected: Boolean?,
28+
/**
29+
* Can be null if the sdk version is not high enough.
30+
*/
31+
val evdevDevices: List<EvdevDeviceInfo>?
2332
) {
2433
companion object {
2534
private val keysThatRequireDndAccess = arrayOf(
@@ -69,6 +78,20 @@ data class TriggerErrorSnapshot(
6978
return TriggerError.DPAD_IME_NOT_SELECTED
7079
}
7180

81+
if (key is EvdevTriggerKey) {
82+
if (isSystemBridgeConnected == null) {
83+
return TriggerError.SYSTEM_BRIDGE_UNSUPPORTED
84+
}
85+
86+
if (!isSystemBridgeConnected) {
87+
return TriggerError.SYSTEM_BRIDGE_DISCONNECTED
88+
}
89+
90+
if (evdevDevices != null && !evdevDevices.contains(key.device)) {
91+
return TriggerError.EVDEV_DEVICE_NOT_FOUND
92+
}
93+
}
94+
7295
return null
7396
}
7497
}

base/src/main/java/io/github/sds100/keymapper/base/trigger/TriggerKeyListItem.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ private fun getErrorMessage(error: TriggerError): String {
264264
TriggerError.FLOATING_BUTTON_DELETED -> stringResource(R.string.trigger_error_floating_button_deleted)
265265
TriggerError.FLOATING_BUTTONS_NOT_PURCHASED -> stringResource(R.string.trigger_error_floating_buttons_not_purchased)
266266
TriggerError.PURCHASE_VERIFICATION_FAILED -> stringResource(R.string.trigger_error_product_verification_failed)
267+
TriggerError.SYSTEM_BRIDGE_UNSUPPORTED -> stringResource(R.string.trigger_error_system_bridge_unsupported)
268+
TriggerError.SYSTEM_BRIDGE_DISCONNECTED -> stringResource(R.string.trigger_error_system_bridge_disconnected)
269+
TriggerError.EVDEV_DEVICE_NOT_FOUND -> stringResource(R.string.trigger_error_evdev_device_not_found)
267270
}
268271
}
269272

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@
7777
<string name="trigger_error_gesture_stroke_count_too_high">Too many fingers to perform gesture due to android limitations.</string>
7878
<string name="trigger_error_gesture_duration_too_high">Gesture duration is too high due to android limitations.</string>
7979
<string name="trigger_error_dpad_ime_not_selected">You must be using the Key Mapper Input Method for DPAD triggers to work!</string>
80+
<string name="trigger_error_system_bridge_unsupported">PRO mode is unsupported on this Android version</string>
81+
<string name="trigger_error_system_bridge_disconnected">PRO mode not started!</string>
82+
<string name="trigger_error_evdev_device_not_found">Trigger device not connected!</string>
8083

8184
<string name="home_error_is_battery_optimised">Your key maps will stop working randomly!</string>
8285
<string name="home_error_key_maps_paused">Your key maps are paused!</string>

0 commit comments

Comments
 (0)