Skip to content

Commit 2e9db96

Browse files
committed
fix: improve how key events are inputted with Shizuku
1 parent 18afd17 commit 2e9db96

13 files changed

Lines changed: 127 additions & 118 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1)
2+
3+
## Bug fixes
4+
5+
- Inputting key events with Shizuku does not crash the app if a Key Mapper keyboard is being used at the same time. And latency when inputting key events has been improved in some apps.
6+
17
## [3.0.0](https://github.com/sds100/KeyMapper/releases/tag/v3.0.0)
28

39
_See the changes from previous 3.0 Beta releases._

app/src/main/java/io/github/sds100/keymapper/UseCases.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ object UseCases {
142142
ServiceLocator.intentAdapter(ctx),
143143
getActionError(ctx),
144144
keyMapperImeMessenger(ctx, keyEventRelayService),
145-
ShizukuInputEventInjector(),
145+
ShizukuInputEventInjector(coroutineScope = ServiceLocator.appCoroutineScope(ctx)),
146146
ServiceLocator.packageManagerAdapter(ctx),
147147
ServiceLocator.appShortcutAdapter(ctx),
148148
ServiceLocator.popupMessageAdapter(ctx),
@@ -179,11 +179,12 @@ object UseCases {
179179
ServiceLocator.audioAdapter(ctx),
180180
keyMapperImeMessenger(ctx, keyEventRelayService),
181181
service,
182-
ShizukuInputEventInjector(),
182+
ShizukuInputEventInjector(ServiceLocator.appCoroutineScope(ctx)),
183183
ServiceLocator.popupMessageAdapter(ctx),
184184
ServiceLocator.permissionAdapter(ctx),
185185
ServiceLocator.resourceProvider(ctx),
186186
ServiceLocator.vibratorAdapter(ctx),
187+
ServiceLocator.appCoroutineScope(ctx),
187188
)
188189

189190
fun rerouteKeyEvents(ctx: Context, keyEventRelayService: KeyEventRelayServiceWrapper) = RerouteKeyEventsUseCaseImpl(

app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ import kotlinx.coroutines.flow.SharingStarted
6767
import kotlinx.coroutines.flow.StateFlow
6868
import kotlinx.coroutines.flow.map
6969
import kotlinx.coroutines.flow.stateIn
70-
import kotlinx.coroutines.launch
7170
import splitties.bitflags.withFlag
7271
import timber.log.Timber
7372

@@ -114,6 +113,7 @@ class PerformActionsUseCaseImpl(
114113
accessibilityService,
115114
shizukuInputEventInjector,
116115
permissionAdapter,
116+
coroutineScope,
117117
)
118118
}
119119

@@ -124,15 +124,15 @@ class PerformActionsUseCaseImpl(
124124
permissionAdapter.isGrantedFlow(Permission.SHIZUKU)
125125
.stateIn(coroutineScope, SharingStarted.Eagerly, false)
126126

127-
override fun perform(
127+
override suspend fun perform(
128128
action: ActionData,
129129
inputEventType: InputEventType,
130130
keyMetaState: Int,
131131
) {
132132
/**
133133
* Is null if the action is being performed asynchronously
134134
*/
135-
val result: Result<*>?
135+
val result: Result<*>
136136

137137
when (action) {
138138
is ActionData.App -> {
@@ -254,20 +254,15 @@ class PerformActionsUseCaseImpl(
254254
}
255255

256256
is ActionData.SwitchKeyboard -> {
257-
coroutineScope.launch {
258-
inputMethodAdapter
259-
.chooseImeWithoutUserInput(action.imeId)
260-
.onSuccess {
261-
val message = resourceProvider.getString(
262-
R.string.toast_chose_keyboard,
263-
it.label,
264-
)
265-
popupMessageAdapter.showPopupMessage(message)
266-
}
267-
.showErrorMessageOnFail()
268-
}
269-
270-
result = null
257+
result = inputMethodAdapter
258+
.chooseImeWithoutUserInput(action.imeId)
259+
.onSuccess {
260+
val message = resourceProvider.getString(
261+
R.string.toast_chose_keyboard,
262+
it.label,
263+
)
264+
popupMessageAdapter.showPopupMessage(message)
265+
}
271266
}
272267

273268
is ActionData.Volume.Down -> {
@@ -578,14 +573,10 @@ class PerformActionsUseCaseImpl(
578573
}
579574

580575
is ActionData.GoLastApp -> {
581-
coroutineScope.launch {
582-
accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
583-
delay(100)
576+
accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
577+
delay(100)
578+
result =
584579
accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_RECENTS)
585-
.showErrorMessageOnFail()
586-
}
587-
588-
result = null
589580
}
590581

591582
is ActionData.OpenMenu -> {
@@ -711,11 +702,11 @@ class PerformActionsUseCaseImpl(
711702

712703
is ActionData.Screenshot -> {
713704
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
714-
coroutineScope.launch {
715-
val picturesFolder = fileAdapter.getPicturesFolder()
716-
val screenshotsFolder = "$picturesFolder/Screenshots"
717-
val fileDate = FileUtils.createFileDate()
705+
val picturesFolder = fileAdapter.getPicturesFolder()
706+
val screenshotsFolder = "$picturesFolder/Screenshots"
707+
val fileDate = FileUtils.createFileDate()
718708

709+
result =
719710
suAdapter.execute("mkdir -p $screenshotsFolder; screencap -p $screenshotsFolder/Screenshot_$fileDate.png")
720711
.onSuccess {
721712
// Wait 3 seconds so the message isn't shown in the screenshot.
@@ -726,9 +717,7 @@ class PerformActionsUseCaseImpl(
726717
R.string.toast_screenshot_taken,
727718
),
728719
)
729-
}.showErrorMessageOnFail()
730-
}
731-
result = null
720+
}
732721
} else {
733722
result =
734723
accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT)
@@ -781,19 +770,11 @@ class PerformActionsUseCaseImpl(
781770
}
782771

783772
ActionData.DismissAllNotifications -> {
784-
coroutineScope.launch {
785-
notificationReceiverAdapter.send(ServiceEvent.DismissAllNotifications)
786-
}
787-
788-
result = null
773+
result = notificationReceiverAdapter.send(ServiceEvent.DismissAllNotifications)
789774
}
790775

791776
ActionData.DismissLastNotification -> {
792-
coroutineScope.launch {
793-
notificationReceiverAdapter.send(ServiceEvent.DismissLastNotification)
794-
}
795-
796-
result = null
777+
result = notificationReceiverAdapter.send(ServiceEvent.DismissLastNotification)
797778
}
798779

799780
ActionData.AnswerCall -> {
@@ -815,27 +796,23 @@ class PerformActionsUseCaseImpl(
815796
}
816797

817798
is ActionData.HttpRequest -> {
818-
coroutineScope.launch {
819-
networkAdapter.sendHttpRequest(
820-
method = action.method,
821-
url = action.url,
822-
body = action.body,
823-
authorizationHeader = action.authorizationHeader,
824-
).showErrorMessageOnFail()
825-
}
826-
827-
result = null
799+
result = networkAdapter.sendHttpRequest(
800+
method = action.method,
801+
url = action.url,
802+
body = action.body,
803+
authorizationHeader = action.authorizationHeader,
804+
)
828805
}
829806
}
830807

831808
when (result) {
832-
null, is Success -> Timber.d("Performed action $action, input event type: $inputEventType, key meta state: $keyMetaState")
809+
is Success -> Timber.d("Performed action $action, input event type: $inputEventType, key meta state: $keyMetaState")
833810
is Error -> Timber.d(
834811
"Failed to perform action $action, reason: ${result.getFullMessage(resourceProvider)}, action: $action, input event type: $inputEventType, key meta state: $keyMetaState",
835812
)
836813
}
837814

838-
result?.showErrorMessageOnFail()
815+
result.showErrorMessageOnFail()
839816
}
840817

841818
override fun getErrorSnapshot(): ActionErrorSnapshot {
@@ -925,7 +902,7 @@ interface PerformActionsUseCase {
925902
val defaultRepeatDelay: Flow<Long>
926903
val defaultRepeatRate: Flow<Long>
927904

928-
fun perform(
905+
suspend fun perform(
929906
action: ActionData,
930907
inputEventType: InputEventType = InputEventType.DOWN_UP,
931908
keyMetaState: Int = 0,

app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ abstract class SimpleMappingController(
127127
}
128128
}
129129

130-
private fun performAction(
130+
private suspend fun performAction(
131131
action: Action,
132132
inputEventType: InputEventType = InputEventType.DOWN_UP,
133133
) {
@@ -172,20 +172,22 @@ abstract class SimpleMappingController(
172172
}
173173

174174
fun reset() {
175-
repeatJobs.values.forEach { jobs ->
175+
for (jobs in repeatJobs.values) {
176176
jobs.forEach { it.cancel() }
177177
}
178178

179179
repeatJobs.clear()
180180

181-
performActionJobs.values.forEach {
182-
it.cancel()
181+
for (job in performActionJobs.values) {
182+
job.cancel()
183183
}
184184

185185
performActionJobs.clear()
186186

187-
actionsBeingHeldDown.forEach {
188-
performAction(it, InputEventType.UP)
187+
coroutineScope.launch {
188+
for (it in actionsBeingHeldDown) {
189+
performAction(it, InputEventType.UP)
190+
}
189191
}
190192

191193
actionsBeingHeldDown.clear()

app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ import io.github.sds100.keymapper.util.InputEventType
3232
import io.github.sds100.keymapper.util.State
3333
import io.github.sds100.keymapper.util.dataOrNull
3434
import io.github.sds100.keymapper.util.ui.ResourceProvider
35+
import kotlinx.coroutines.CoroutineScope
3536
import kotlinx.coroutines.Dispatchers
3637
import kotlinx.coroutines.flow.Flow
3738
import kotlinx.coroutines.flow.combine
3839
import kotlinx.coroutines.flow.flowOn
3940
import kotlinx.coroutines.flow.map
41+
import kotlinx.coroutines.launch
42+
import kotlinx.coroutines.runBlocking
4043
import timber.log.Timber
4144

4245
/**
@@ -58,6 +61,7 @@ class DetectKeyMapsUseCaseImpl(
5861
private val permissionAdapter: PermissionAdapter,
5962
private val resourceProvider: ResourceProvider,
6063
private val vibrator: VibratorAdapter,
64+
private val coroutineScope: CoroutineScope,
6165
) : DetectKeyMapsUseCase {
6266

6367
companion object {
@@ -164,6 +168,7 @@ class DetectKeyMapsUseCaseImpl(
164168
accessibilityService,
165169
shizukuInputEventInjector,
166170
permissionAdapter,
171+
coroutineScope,
167172
)
168173

169174
override val forceVibrate: Flow<Boolean> =
@@ -189,18 +194,20 @@ class DetectKeyMapsUseCaseImpl(
189194
inputEventType: InputEventType,
190195
scanCode: Int,
191196
) {
197+
val model = InputKeyModel(
198+
keyCode,
199+
inputEventType,
200+
metaState,
201+
deviceId,
202+
scanCode,
203+
)
204+
192205
if (permissionAdapter.isGranted(Permission.SHIZUKU)) {
193206
Timber.d("Imitate button press ${KeyEvent.keyCodeToString(keyCode)} with Shizuku, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode")
194207

195-
shizukuInputEventInjector.inputKeyEvent(
196-
InputKeyModel(
197-
keyCode,
198-
inputEventType,
199-
metaState,
200-
deviceId,
201-
scanCode,
202-
),
203-
)
208+
coroutineScope.launch {
209+
shizukuInputEventInjector.inputKeyEvent(model)
210+
}
204211
} else {
205212
Timber.d("Imitate button press ${KeyEvent.keyCodeToString(keyCode)}, key code: $keyCode, device id: $deviceId, meta state: $metaState, scan code: $scanCode")
206213

@@ -217,15 +224,9 @@ class DetectKeyMapsUseCaseImpl(
217224

218225
KeyEvent.KEYCODE_MENU -> openMenuHelper.openMenu()
219226

220-
else -> imeInputEventInjector.inputKeyEvent(
221-
InputKeyModel(
222-
keyCode,
223-
inputEventType,
224-
metaState,
225-
deviceId,
226-
scanCode,
227-
),
228-
)
227+
else -> runBlocking {
228+
imeInputEventInjector.inputKeyEvent(model)
229+
}
229230
}
230231
}
231232
}

0 commit comments

Comments
 (0)