diff --git a/core/input/gamepad_device.h b/core/input/gamepad_device.h index a001ea6c9..7ad8f5c1b 100644 --- a/core/input/gamepad_device.h +++ b/core/input/gamepad_device.h @@ -93,6 +93,7 @@ protected: InputMapping *input_mapper; std::map axis_min_values; std::map axis_ranges; + bool _rumble_enabled = true; private: int get_axis_min_value(u32 axis); @@ -105,8 +106,6 @@ private: input_detected_cb _input_detected; bool _remappable; - bool _rumble_enabled = true; - static std::vector> _gamepads; static std::mutex _gamepads_mutex; }; diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java index 8ccee3357..b6131e5cc 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/Emulator.java @@ -2,6 +2,7 @@ package com.reicast.emulator; import android.app.Activity; import android.app.Application; +import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.support.v7.app.AppCompatDelegate; @@ -10,6 +11,8 @@ import android.util.Log; import com.reicast.emulator.emu.JNIdc; public class Emulator extends Application { + private static Context context; + public static final String pref_dynarecopt = "dynarec_opt"; public static final String pref_unstable = "unstable_opt"; public static final String pref_dynsafemode = "dyn_safemode"; @@ -262,6 +265,16 @@ public class Emulator extends Application { return false; } + @Override + public void onCreate() { + super.onCreate(); + Emulator.context = getApplicationContext(); + } + + public static Context getAppContext() { + return Emulator.context; + } + static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); } diff --git a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/periph/InputDeviceManager.java b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/periph/InputDeviceManager.java index 11037839c..323065aef 100644 --- a/shell/android-studio/reicast/src/main/java/com/reicast/emulator/periph/InputDeviceManager.java +++ b/shell/android-studio/reicast/src/main/java/com/reicast/emulator/periph/InputDeviceManager.java @@ -2,9 +2,11 @@ package com.reicast.emulator.periph; import android.content.Context; import android.hardware.input.InputManager; -import android.util.Log; +import android.os.Vibrator; import android.view.InputDevice; +import com.reicast.emulator.Emulator; + public final class InputDeviceManager implements InputManager.InputDeviceListener { public static final int VIRTUAL_GAMEPAD_ID = 0x12345678; @@ -13,6 +15,11 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene private InputManager inputManager; private int maple_port = 0; + public InputDeviceManager() + { + init(); + } + public void startListening(Context applicationContext) { maple_port = 0; @@ -55,10 +62,34 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene public void onInputDeviceChanged(int i) { } + // Called from native code + private boolean Rumble(int i, float power, float inclination, int duration_ms) { + Vibrator vibrator; + if (i == VIRTUAL_GAMEPAD_ID) { + vibrator = (Vibrator)Emulator.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); + } + else { + InputDevice device = InputDevice.getDevice(i); + if (device == null) + return false; + vibrator = device.getVibrator(); + if (!vibrator.hasVibrator()) + return false; + } + // TODO API >= 26 (Android 8.0) + if (power == 0) + vibrator.cancel(); + else + vibrator.vibrate(duration_ms); + + return true; + } + public static InputDeviceManager getInstance() { return INSTANCE; } + public native void init(); public native void virtualGamepadEvent(int kcode, int joyx, int joyy, int lt, int rt); public native boolean joystickButtonEvent(int id, int button, boolean pressed); public native boolean joystickAxisEvent(int id, int button, int value); diff --git a/shell/android-studio/reicast/src/main/jni/src/Android.cpp b/shell/android-studio/reicast/src/main/jni/src/Android.cpp index 5165b8508..2876cd9da 100644 --- a/shell/android-studio/reicast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/reicast/src/main/jni/src/Android.cpp @@ -22,6 +22,50 @@ #include "reios/reios.h" #include "imgread/common.h" #include "rend/gui.h" + +JavaVM* g_jvm; + +// Convenience class to get the java environment for the current thread. +// Also attach the threads, and detach it on destruction, if needed. This is probably not very efficient +// but shouldn't be needed except for error reporting. +class JVMAttacher { +public: + JVMAttacher() : env(NULL), detach_thread(false) { + if (g_jvm == NULL) { + log_error("g_jvm == NULL"); + return; + } + int rc = g_jvm->GetEnv((void **)&env, JNI_VERSION_1_6); + if (rc == JNI_EDETACHED) { + if (g_jvm->AttachCurrentThread(&env, NULL) != 0) { + log_error("AttachCurrentThread failed"); + return; + } + detach_thread = true; + } + else if (rc == JNI_EVERSION) { + log_error("JNI version error"); + return; + } + } + + ~JVMAttacher() + { + if (detach_thread) + g_jvm->DetachCurrentThread(); + } + + void log_error(const char *reason) + { + LOGE("JVMAttacher cannot attach to JVM: %s", reason); + } + + bool failed() { return env == NULL; } + + JNIEnv *env; + bool detach_thread = false; +}; + #include "android_gamepad.h" #define SETTINGS_ACCESSORS(jsetting, csetting, type) \ @@ -101,6 +145,7 @@ JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_screenDpi(JNIEnv *env JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_guiOpenSettings(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); JNIEXPORT jboolean JNICALL Java_com_reicast_emulator_emu_JNIdc_guiIsOpen(JNIEnv *env,jobject obj) __attribute__((visibility("default"))); +JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_init(JNIEnv *env, jobject obj) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj, jint id, jstring name, jint maple_port) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickRemoved(JNIEnv *env, jobject obj, jint id) __attribute__((visibility("default"))); JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_virtualGamepadEvent(JNIEnv *env, jobject obj, jint kcode, jint joyx, jint joyy, jint lt, jint rt) __attribute__((visibility("default"))); @@ -151,7 +196,6 @@ extern u32 mo_buttons; extern bool print_stats; //stuff for saving prefs -JavaVM* g_jvm; jobject g_emulator; jmethodID saveSettingsMid; static ANativeWindow *g_window = 0; @@ -341,47 +385,6 @@ jobject vmulcd = NULL; jbyteArray jpix = NULL; jmethodID updatevmuscreen; -// Convenience class to get the java environment for the current thread. -// Also attach the threads, and detach it on destruction, if needed. This is probably not very efficient -// but shouldn't be needed except for error reporting. -class JVMAttacher { -public: - JVMAttacher() : env(NULL), detach_thread(false) { - if (g_jvm == NULL) { - log_error("g_jvm == NULL"); - return; - } - int rc = g_jvm->GetEnv((void **)&env, JNI_VERSION_1_6); - if (rc == JNI_EDETACHED) { - if (g_jvm->AttachCurrentThread(&env, NULL) != 0) { - log_error("AttachCurrentThread failed"); - return; - } - detach_thread = true; - } - else if (rc == JNI_EVERSION) { - log_error("JNI version error"); - return; - } - } - - ~JVMAttacher() - { - if (detach_thread) - g_jvm->DetachCurrentThread(); - } - - void log_error(const char *reason) - { - LOGE("JVMAttacher cannot attach to JVM: %s", reason); - } - - bool failed() { return env == NULL; } - - JNIEnv *env; - bool detach_thread = false; -}; - JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_query(JNIEnv *env,jobject obj,jobject emu_thread) { jmethodID reiosInfoMid=env->GetMethodID(env->GetObjectClass(emu_thread),"reiosInfo","(Ljava/lang/String;Ljava/lang/String;)V"); @@ -709,6 +712,12 @@ void os_DebugBreak() for(;;) ; } +JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_init(JNIEnv *env, jobject obj) +{ + input_device_manager = env->NewGlobalRef(obj); + input_device_manager_rumble = env->GetMethodID(env->GetObjectClass(obj), "Rumble", "(IFFI)Z"); +} + JNIEXPORT void JNICALL Java_com_reicast_emulator_periph_InputDeviceManager_joystickAdded(JNIEnv *env, jobject obj, jint id, jstring name, jint maple_port) { const char* joyname = env->GetStringUTFChars(name,0); diff --git a/shell/android-studio/reicast/src/main/jni/src/android_gamepad.h b/shell/android-studio/reicast/src/main/jni/src/android_gamepad.h index 8d28152f7..2fee7b5f7 100644 --- a/shell/android-studio/reicast/src/main/jni/src/android_gamepad.h +++ b/shell/android-studio/reicast/src/main/jni/src/android_gamepad.h @@ -19,6 +19,9 @@ #include "input/gamepad_device.h" +static jobject input_device_manager; +static jmethodID input_device_manager_rumble; + enum { AXIS_X = 0, AXIS_Y = 1, @@ -138,6 +141,15 @@ public: previous_kcode = kcode; } + void rumble(float power, float inclination, u32 duration_ms) override + { + JVMAttacher jvm_attacher; + if (jvm_attacher.failed()) + return; + jboolean has_vibrator = jvm_attacher.env->CallBooleanMethod(input_device_manager, input_device_manager_rumble, android_id, power, inclination, duration_ms); + _rumble_enabled = has_vibrator; + } + static const int VIRTUAL_GAMEPAD_ID = 0x12345678; // must match the Java definition protected: