Android: Make input state changes observable

This commit is contained in:
JosJuice
2025-05-13 16:55:48 +02:00
parent 903eafcf65
commit 4214cb6eb8
2 changed files with 47 additions and 6 deletions

View File

@ -6,6 +6,7 @@ import android.content.Context
import android.hardware.input.InputManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
@ -13,8 +14,11 @@ import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import androidx.annotation.Keep
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.dolphinemu.dolphinemu.DolphinApplication
import org.dolphinemu.dolphinemu.utils.LooperThread
import java.util.concurrent.atomic.AtomicBoolean
/**
* This class interfaces with the native ControllerInterface,
@ -24,6 +28,12 @@ object ControllerInterface {
private var inputDeviceListener: InputDeviceListener? = null
private lateinit var looperThread: LooperThread
private var inputStateUpdatePending = AtomicBoolean(false)
private val inputStateVersion = MutableLiveData(0)
val inputStateChanged: LiveData<Int>
get() = inputStateVersion
/**
* Activities which want to pass on inputs to native code
* should call this in their own dispatchKeyEvent method.
@ -31,7 +41,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the event should be passed on to the default dispatchKeyEvent.
*/
external fun dispatchKeyEvent(event: KeyEvent): Boolean
fun dispatchKeyEvent(event: KeyEvent): Boolean {
val result = dispatchKeyEventNative(event)
onInputStateChanged()
return result
}
private external fun dispatchKeyEventNative(event: KeyEvent): Boolean
/**
* Activities which want to pass on inputs to native code
@ -40,7 +56,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the event should be passed on to the default dispatchGenericMotionEvent.
*/
external fun dispatchGenericMotionEvent(event: MotionEvent): Boolean
fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
val result = dispatchGenericMotionEventNative(event)
onInputStateChanged()
return result
}
private external fun dispatchGenericMotionEventNative(event: MotionEvent): Boolean
/**
* [DolphinSensorEventListener] calls this for each axis of a received SensorEvent.
@ -48,7 +70,13 @@ object ControllerInterface {
* @return true if the emulator core seems to be interested in this event.
* false if the sensor can be suspended to save battery.
*/
external fun dispatchSensorEvent(
fun dispatchSensorEvent(deviceQualifier: String, axisName: String, value: Float): Boolean {
val result = dispatchSensorEventNative(deviceQualifier, axisName, value)
onInputStateChanged()
return result
}
private external fun dispatchSensorEventNative(
deviceQualifier: String,
axisName: String,
value: Float
@ -76,6 +104,19 @@ object ControllerInterface {
external fun getDevice(deviceString: String): CoreDevice?
private fun onInputStateChanged() {
// When a single SensorEvent is dispatched, this method is likely to get called many times.
// For the sake of performance, let's batch input state updates so that observers only have
// to process one update.
if (!inputStateUpdatePending.getAndSet(true)) {
Handler(Looper.getMainLooper()).post {
if (inputStateUpdatePending.getAndSet(false)) {
inputStateVersion.value = inputStateVersion.value?.plus(1)
}
}
}
}
@Keep
@JvmStatic
private fun registerInputDeviceListener() {