From 104ea098921846e01eb58ebfa00822b14da46cda Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Mar 2022 20:27:25 +0100 Subject: [PATCH] ControllerInterface/Android: Implement hotplug --- .../input/model/ControllerInterface.java | 72 +++++++++++++++++++ .../dolphinemu/utils/LooperThread.java | 58 +++++++++++++++ .../ControllerInterface/Android/Android.cpp | 27 +++++++ 3 files changed, 157 insertions(+) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java index 80d21eeb57..8a7bc8177f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java @@ -2,15 +2,50 @@ package org.dolphinemu.dolphinemu.features.input.model; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.Handler; import android.view.KeyEvent; import android.view.MotionEvent; +import androidx.annotation.Keep; + +import org.dolphinemu.dolphinemu.DolphinApplication; +import org.dolphinemu.dolphinemu.utils.LooperThread; + /** * This class interfaces with the native ControllerInterface, * which is where the emulator core gets inputs from. */ public final class ControllerInterface { + private static final class InputDeviceListener implements InputManager.InputDeviceListener + { + @Override + public void onInputDeviceAdded(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + + @Override + public void onInputDeviceChanged(int deviceId) + { + // Simple implementation for now. We could do something fancier if we wanted to. + refreshDevices(); + } + } + + private static InputDeviceListener mInputDeviceListener; + private static LooperThread mLooperThread; + /** * Activities which want to pass on inputs to native code * should call this in their own dispatchKeyEvent method. @@ -28,4 +63,41 @@ public final class ControllerInterface * false if the event should be passed on to the default dispatchGenericMotionEvent. */ public static native boolean dispatchGenericMotionEvent(MotionEvent event); + + /** + * Rescans for input devices. + */ + public static native void refreshDevices(); + + @Keep + private static void registerInputDeviceListener() + { + if (mLooperThread == null) + { + mLooperThread = new LooperThread("Hotplug thread"); + mLooperThread.start(); + } + + if (mInputDeviceListener == null) + { + InputManager im = (InputManager) + DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); + + mInputDeviceListener = new InputDeviceListener(); + im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper())); + } + } + + @Keep + private static void unregisterInputDeviceListener() + { + if (mInputDeviceListener != null) + { + InputManager im = (InputManager) + DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); + + im.unregisterInputDeviceListener(mInputDeviceListener); + mInputDeviceListener = null; + } + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java new file mode 100644 index 0000000000..8a67ec7e89 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/LooperThread.java @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.utils; + +import android.os.Looper; + +public class LooperThread extends Thread +{ + private Looper mLooper; + + public LooperThread() + { + super(); + } + + public LooperThread(String name) + { + super(name); + } + + @Override + public void run() + { + Looper.prepare(); + + synchronized (this) + { + mLooper = Looper.myLooper(); + notifyAll(); + } + + Looper.loop(); + } + + public Looper getLooper() + { + if (!isAlive()) + { + throw new IllegalStateException(); + } + + synchronized (this) + { + while (mLooper == null) + { + try + { + wait(); + } + catch (InterruptedException ignored) + { + } + } + } + + return mLooper; + } +} diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp index 5a16ef56d0..e5baa4e43e 100644 --- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp @@ -58,6 +58,10 @@ jclass s_motion_event_class; jmethodID s_motion_event_get_axis_value; jmethodID s_motion_event_get_source; +jclass s_controller_interface_class; +jmethodID s_controller_interface_register_input_device_listener; +jmethodID s_controller_interface_unregister_input_device_listener; + jintArray s_keycodes_array; using Clock = std::chrono::steady_clock; @@ -635,20 +639,36 @@ void Init() s_motion_event_get_axis_value = env->GetMethodID(s_motion_event_class, "getAxisValue", "(I)F"); s_motion_event_get_source = env->GetMethodID(s_motion_event_class, "getSource", "()I"); + const jclass controller_interface_class = + env->FindClass("org/dolphinemu/dolphinemu/features/input/model/ControllerInterface"); + s_controller_interface_class = + reinterpret_cast(env->NewGlobalRef(controller_interface_class)); + s_controller_interface_register_input_device_listener = + env->GetStaticMethodID(s_controller_interface_class, "registerInputDeviceListener", "()V"); + s_controller_interface_unregister_input_device_listener = + env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V"); + jintArray keycodes_array = CreateKeyCodesArray(env); s_keycodes_array = reinterpret_cast(env->NewGlobalRef(keycodes_array)); env->DeleteLocalRef(keycodes_array); + + env->CallStaticVoidMethod(s_controller_interface_class, + s_controller_interface_register_input_device_listener); } void Shutdown() { JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(s_controller_interface_class, + s_controller_interface_unregister_input_device_listener); + env->DeleteGlobalRef(s_input_device_class); env->DeleteGlobalRef(s_motion_range_class); env->DeleteGlobalRef(s_input_event_class); env->DeleteGlobalRef(s_key_event_class); env->DeleteGlobalRef(s_motion_event_class); + env->DeleteGlobalRef(s_controller_interface_class); env->DeleteGlobalRef(s_keycodes_array); } @@ -785,4 +805,11 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatch return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT; } + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env, + jclass) +{ + g_controller_interface.RefreshDevices(); +} }