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 2b6e1d62ee..0d6a078b4e 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 @@ -4,11 +4,18 @@ package org.dolphinemu.dolphinemu.features.input.model; import android.content.Context; import android.hardware.input.InputManager; +import android.os.Build; import android.os.Handler; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.os.VibratorManager; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.dolphinemu.dolphinemu.DolphinApplication; import org.dolphinemu.dolphinemu.utils.LooperThread; @@ -119,4 +126,48 @@ public final class ControllerInterface mInputDeviceListener = null; } } + + @Keep @NonNull + private static DolphinVibratorManager getVibratorManager(InputDevice device) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + return new DolphinVibratorManagerPassthrough(device.getVibratorManager()); + } + else + { + return new DolphinVibratorManagerCompat(device.getVibrator()); + } + } + + @Keep @NonNull + private static DolphinVibratorManager getSystemVibratorManager() + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + { + VibratorManager vibratorManager = (VibratorManager) + DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE); + + if (vibratorManager != null) + return new DolphinVibratorManagerPassthrough(vibratorManager); + } + + Vibrator vibrator = (Vibrator) + DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); + + return new DolphinVibratorManagerCompat(vibrator); + } + + @Keep + private static void vibrate(@NonNull Vibrator vibrator) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); + } + else + { + vibrator.vibrate(100); + } + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java new file mode 100644 index 0000000000..abc04d969d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Vibrator; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +/** + * A wrapper around {@link android.os.VibratorManager}, for backwards compatibility. + */ +public interface DolphinVibratorManager +{ + @Keep @NonNull + Vibrator getVibrator(int vibratorId); + + @Keep @NonNull + int[] getVibratorIds(); +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java new file mode 100644 index 0000000000..40c4fa95d1 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Vibrator; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public final class DolphinVibratorManagerCompat implements DolphinVibratorManager +{ + private final Vibrator mVibrator; + private final int[] mIds; + + public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator) + { + mVibrator = vibrator; + mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{}; + } + + @Override @NonNull + public Vibrator getVibrator(int vibratorId) + { + if (vibratorId > mIds.length) + throw new IndexOutOfBoundsException(); + + return mVibrator; + } + + @Override @NonNull + public int[] getVibratorIds() + { + return mIds; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java new file mode 100644 index 0000000000..2ca747f54f --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model; + +import android.os.Build; +import android.os.Vibrator; +import android.os.VibratorManager; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@RequiresApi(api = Build.VERSION_CODES.S) +public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager +{ + private final VibratorManager mVibratorManager; + + public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager) + { + mVibratorManager = vibratorManager; + } + + @Override @NonNull + public Vibrator getVibrator(int vibratorId) + { + return mVibratorManager.getVibrator(vibratorId); + } + + @Override @NonNull + public int[] getVibratorIds() + { + return mVibratorManager.getVibratorIds(); + } +} diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp index 058a9e3171..811343bf73 100644 --- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp @@ -62,6 +62,9 @@ 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; +jmethodID s_controller_interface_get_vibrator_manager; +jmethodID s_controller_interface_get_system_vibrator_manager; +jmethodID s_controller_interface_vibrate; jclass s_sensor_event_listener_class; jmethodID s_sensor_event_listener_constructor; @@ -71,6 +74,10 @@ jmethodID s_sensor_event_listener_request_unsuspend_sensor; jmethodID s_sensor_event_listener_get_axis_names; jmethodID s_sensor_event_listener_get_negative_axes; +jclass s_dolphin_vibrator_manager_class; +jmethodID s_dolphin_vibrator_manager_get_vibrator; +jmethodID s_dolphin_vibrator_manager_get_vibrator_ids; + jintArray s_keycodes_array; using Clock = std::chrono::steady_clock; @@ -534,6 +541,35 @@ private: std::atomic m_is_suspended = true; }; +class AndroidMotor : public Core::Device::Output +{ +public: + AndroidMotor(JNIEnv* env, jobject vibrator, jint id) + : m_vibrator(env->NewGlobalRef(vibrator)), m_id(id) + { + } + + ~AndroidMotor() { IDCache::GetEnvForThread()->DeleteGlobalRef(m_vibrator); } + + std::string GetName() const override { return "Motor " + std::to_string(m_id); } + + void SetState(ControlState state) override + { + ControlState old_state = m_state.exchange(state, std::memory_order_relaxed); + + if (old_state < 0.5 && state >= 0.5) + { + IDCache::GetEnvForThread()->CallStaticVoidMethod(s_controller_interface_class, + s_controller_interface_vibrate, m_vibrator); + } + } + +private: + const jobject m_vibrator; + const jint m_id; + std::atomic m_state = 0; +}; + class AndroidDevice final : public Core::Device { public: @@ -551,6 +587,7 @@ public: AddKeys(env, input_device); AddAxes(env, input_device); + AddMotors(env, input_device); } // Constructor for the device added by Dolphin to contain sensor inputs @@ -558,6 +595,7 @@ public: : m_sensor_event_listener(AddSensors(env, nullptr)), m_source(AINPUT_SOURCE_SENSOR), m_controller_number(0), m_name(std::move(name)) { + AddSystemMotors(env); } ~AndroidDevice() @@ -687,6 +725,41 @@ private: return sensor_event_listener; } + void AddMotors(JNIEnv* env, jobject input_device) + { + jobject vibrator_manager = env->CallStaticObjectMethod( + s_controller_interface_class, s_controller_interface_get_vibrator_manager, input_device); + AddMotorsFromManager(env, vibrator_manager); + env->DeleteLocalRef(vibrator_manager); + } + + void AddSystemMotors(JNIEnv* env) + { + jobject vibrator_manager = env->CallStaticObjectMethod( + s_controller_interface_class, s_controller_interface_get_system_vibrator_manager); + AddMotorsFromManager(env, vibrator_manager); + env->DeleteLocalRef(vibrator_manager); + } + + void AddMotorsFromManager(JNIEnv* env, jobject vibrator_manager) + { + jintArray j_vibrator_ids = reinterpret_cast( + env->CallObjectMethod(vibrator_manager, s_dolphin_vibrator_manager_get_vibrator_ids)); + jint* vibrator_ids = env->GetIntArrayElements(j_vibrator_ids, nullptr); + + jint size = env->GetArrayLength(j_vibrator_ids); + for (jint i = 0; i < size; ++i) + { + jobject vibrator = + env->CallObjectMethod(vibrator_manager, s_dolphin_vibrator_manager_get_vibrator, i); + AddOutput(new AndroidMotor(env, vibrator, i)); + env->DeleteLocalRef(vibrator); + } + + env->ReleaseIntArrayElements(j_vibrator_ids, vibrator_ids, 0); + env->DeleteLocalRef(j_vibrator_ids); + } + const jobject m_sensor_event_listener; const int m_source; const int m_controller_number; @@ -764,6 +837,15 @@ void Init() env->GetStaticMethodID(s_controller_interface_class, "registerInputDeviceListener", "()V"); s_controller_interface_unregister_input_device_listener = env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V"); + s_controller_interface_get_vibrator_manager = + env->GetStaticMethodID(s_controller_interface_class, "getVibratorManager", + "(Landroid/view/InputDevice;)Lorg/dolphinemu/dolphinemu/features/" + "input/model/DolphinVibratorManager;"); + s_controller_interface_get_system_vibrator_manager = env->GetStaticMethodID( + s_controller_interface_class, "getSystemVibratorManager", + "()Lorg/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager;"); + s_controller_interface_vibrate = + env->GetStaticMethodID(s_controller_interface_class, "vibrate", "(Landroid/os/Vibrator;)V"); const jclass sensor_event_listener_class = env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener"); @@ -782,6 +864,15 @@ void Init() s_sensor_event_listener_get_negative_axes = env->GetMethodID(s_sensor_event_listener_class, "getNegativeAxes", "()[Z"); + const jclass dolphin_vibrator_manager_class = + env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager"); + s_dolphin_vibrator_manager_class = + reinterpret_cast(env->NewGlobalRef(dolphin_vibrator_manager_class)); + s_dolphin_vibrator_manager_get_vibrator = + env->GetMethodID(s_dolphin_vibrator_manager_class, "getVibrator", "(I)Landroid/os/Vibrator;"); + s_dolphin_vibrator_manager_get_vibrator_ids = + env->GetMethodID(s_dolphin_vibrator_manager_class, "getVibratorIds", "()[I"); + jintArray keycodes_array = CreateKeyCodesArray(env); s_keycodes_array = reinterpret_cast(env->NewGlobalRef(keycodes_array)); env->DeleteLocalRef(keycodes_array); @@ -804,6 +895,7 @@ void Shutdown() env->DeleteGlobalRef(s_motion_event_class); env->DeleteGlobalRef(s_controller_interface_class); env->DeleteGlobalRef(s_sensor_event_listener_class); + env->DeleteGlobalRef(s_dolphin_vibrator_manager_class); env->DeleteGlobalRef(s_keycodes_array); }