summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2025-03-16 11:05:06 +0100
committerJosJuice <josjuice@gmail.com>2025-03-23 21:39:04 +0100
commit516c1314d2e08a81aa707566265597d82591e22c (patch)
tree6412ab67ada4d97456772b96bdbe68ac6f104540 /Source/Android/app/src/main/java
parent1515cf6ccdd1ba206c8e0eaabad07de28a4f5939 (diff)
Android: Don't use separate thread for MotionAlertDialog
This is an Android continuation of bc95c00. We now call InputDetector::Update immediately after receiving an input event from Android instead of periodically calling it in a sleep loop. This improves detection of very short inputs, which are especially likely to occur for volume buttons on phones (or at least on my phone) if you don't intentionally keep them held down.
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputDetector.kt68
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt16
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt43
3 files changed, 100 insertions, 27 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..c3b471899e 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,14 @@
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 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 +26,15 @@ 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()
}
override fun onStop() {
@@ -48,9 +44,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 +61,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)
+ }
+ }
}