ControllerInterface/Android: Implement rumble

This commit is contained in:
JosJuice 2022-09-18 19:14:15 +02:00
parent 065481d989
commit 8e33458f48
5 changed files with 231 additions and 0 deletions

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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<bool> 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<ControlState> 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<jintArray>(
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<jclass>(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<jintArray>(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);
}