diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt index 3bca59f7b7..d5945f2784 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt @@ -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 + 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() { diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp index b6e03e73a0..72d7239f9a 100644 --- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp @@ -1002,7 +1002,7 @@ void InputBackend::PopulateDevices() extern "C" { JNIEXPORT jboolean JNICALL -Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchKeyEvent( +Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchKeyEventNative( JNIEnv* env, jclass, jobject key_event) { const jint action = env->CallIntMethod(key_event, s_key_event_get_action); @@ -1046,7 +1046,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch } JNIEXPORT jboolean JNICALL -Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchGenericMotionEvent( +Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchGenericMotionEventNative( JNIEnv* env, jclass, jobject motion_event) { const jint device_id = env->CallIntMethod(motion_event, s_input_event_get_device_id); @@ -1090,7 +1090,7 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch } JNIEXPORT jboolean JNICALL -Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEvent( +Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEventNative( JNIEnv* env, jclass, jstring j_device_qualifier, jstring j_axis_name, jfloat value) { ciface::Core::DeviceQualifier device_qualifier;