diff options
| author | JMC47 <JMC4789@gmail.com> | 2025-03-28 18:25:04 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-28 18:25:04 -0400 |
| commit | 7d794897c4043b9ee8b278340cfeff02923512f4 (patch) | |
| tree | 05205ca6145a2450d3b3cc5e14526f83f8a379da /Source/Android/app/src | |
| parent | e0032b3e2c821f11c1c2964d4901399e88429898 (diff) | |
| parent | 9e9faf3be1582ac5cdcaa46dc60287b10c921aa2 (diff) | |
Merge pull request #13434 from JosJuice/android-non-blocking-input-detection
Android: Don't use separate thread for MotionAlertDialog
Diffstat (limited to 'Source/Android/app/src')
4 files changed, 110 insertions, 28 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputDetector.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputDetector.kt new file mode 100644 index 0000000000..e180814a58 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputDetector.kt @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import androidx.annotation.Keep + +/** + * Waits for the user to press inputs, and reports which inputs were pressed. + * + * The caller is responsible for forwarding input events from Android to ControllerInterface + * and then calling [update]. + */ +class InputDetector { + @Keep + private val pointer: Long + + constructor() { + pointer = createNew() + } + + @Keep + private constructor(pointer: Long) { + this.pointer = pointer + } + + external fun finalize() + + private external fun createNew(): Long + + /** + * Starts a detection session. + * + * @param defaultDevice The device to detect inputs from. + * @param allDevices Whether to also detect inputs from devices other than the specified one. + */ + external fun start(defaultDevice: String, allDevices: Boolean) + + /** + * Checks what inputs are currently pressed and updates internal state. + * + * During a detection session, this should be called after each call to + * [ControllerInterface.dispatchKeyEvent] and [ControllerInterface#dispatchGenericMotionEvent]. + */ + external fun update() + + /** + * Returns whether a detection session has finished. + * + * A detection session can end once the user has pressed and released an input or once a timeout + * has been reached. + */ + external fun isComplete(): Boolean + + /** + * Returns the result of a detection session. + * + * The result of each detection session is only returned once. If this method is called more + * than once without starting a new detection session, the second call onwards will return an + * empty string. + * + * @param defaultDevice The device to detect inputs from. Should normally be the same as the one + * passed to [start]. + * + * @return The input(s) pressed by the user in the form of an InputCommon expression, + * or an empty string if there were no inputs. + */ + external fun takeResults(defaultDevice: String): String +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt index 32775a7ebc..622f4f5515 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt @@ -2,23 +2,7 @@ package org.dolphinemu.dolphinemu.features.input.model -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController - object MappingCommon { - /** - * Waits until the user presses one or more inputs or until a timeout, - * then returns the pressed inputs. - * - * When this is being called, a separate thread must be calling ControllerInterface's - * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. - * - * @param controller The device to detect inputs from. - * @param allDevices Whether to also detect inputs from devices other than the specified one. - * @return The input(s) pressed by the user in the form of an InputCommon expression, - * or an empty string if there were no inputs. - */ - external fun detectInput(controller: EmulatedController, allDevices: Boolean): String - external fun getExpressionForControl( control: String, device: String, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt index ff4a278f4c..12f3ffb138 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt @@ -3,12 +3,16 @@ package org.dolphinemu.dolphinemu.features.input.ui import android.app.Activity +import android.os.Looper +import android.os.Handler import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface -import org.dolphinemu.dolphinemu.features.input.model.MappingCommon +import org.dolphinemu.dolphinemu.features.input.model.InputDetector import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting /** @@ -24,21 +28,21 @@ class MotionAlertDialog( private val setting: InputMappingControlSetting, private val allDevices: Boolean ) : AlertDialog(activity) { + private val handler = Handler(Looper.getMainLooper()) + private val inputDetector: InputDetector = InputDetector() private var running = false override fun onStart() { super.onStart() - running = true - Thread { - val result = MappingCommon.detectInput(setting.controller, allDevices) - activity.runOnUiThread { - if (running) { - setting.value = result - dismiss() - } - } - }.start() + inputDetector.start(setting.controller.getDefaultDevice(), allDevices) + periodicUpdate() + if (running == false) { + MaterialAlertDialogBuilder(activity) + .setMessage(R.string.input_binding_disconnected_device) + .setPositiveButton(R.string.ok, null) + .show() + } } override fun onStop() { @@ -48,9 +52,11 @@ class MotionAlertDialog( override fun dispatchKeyEvent(event: KeyEvent): Boolean { ControllerInterface.dispatchKeyEvent(event) + updateInputDetector() if (event.keyCode == KeyEvent.KEYCODE_BACK && event.isLongPress) { // Special case: Let the user cancel by long-pressing Back (intended for non-touch devices) setting.clearValue() + running = false dismiss() } return true @@ -63,6 +69,29 @@ class MotionAlertDialog( } ControllerInterface.dispatchGenericMotionEvent(event) + updateInputDetector() return true } + + private fun updateInputDetector() { + if (running) { + if (inputDetector.isComplete()) { + setting.value = inputDetector.takeResults(setting.controller.getDefaultDevice()) + running = false + + // Quirk: If this method has been called from onStart, calling dismiss directly + // doesn't seem to do anything. As a workaround, post a call to dismiss instead. + handler.post(this::dismiss) + } else { + inputDetector.update() + } + } + } + + private fun periodicUpdate() { + updateInputDetector() + if (running) { + handler.postDelayed(this::periodicUpdate, 10) + } + } } diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index e10a781114..c0b33237af 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -54,7 +54,8 @@ <string name="input_binding">Input Binding</string> <string name="input_binding_description">Press or move an input to bind it to %1$s.</string> - <string name="input_binding_no_device">You need to select a device first!</string> + <string name="input_binding_no_device">You need to select a device first.</string> + <string name="input_binding_disconnected_device">The selected device is disconnected.\n\nPlease reconnect the device or select a different device.</string> <string name="input_configure_input">Configure Input</string> <string name="input_configure_output">Configure Output</string> <string name="input_expression">Expression</string> |
