diff --git a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java index f0e5540db..3a16832c5 100644 --- a/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java +++ b/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java @@ -9,6 +9,7 @@ import android.content.Context; import android.os.Build; import android.os.VibrationEffect; import android.os.Vibrator; +import android.os.VibratorManager; import android.util.Log; import android.view.InputDevice; import android.view.KeyEvent; @@ -24,7 +25,7 @@ public class SDLControllerManager public static native int nativeAddJoystick(int device_id, String name, String desc, int vendor_id, int product_id, int button_mask, - int naxes, int axis_mask, int nhats); + int naxes, int axis_mask, int nhats, boolean can_rumble); public static native int nativeRemoveJoystick(int device_id); public static native int nativeAddHaptic(int device_id, String name); public static native int nativeRemoveHaptic(int device_id); @@ -50,7 +51,9 @@ public class SDLControllerManager } if (mHapticHandler == null) { - if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { + if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { + mHapticHandler = new SDLHapticHandler_API31(); + } else if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) { mHapticHandler = new SDLHapticHandler_API26(); } else { mHapticHandler = new SDLHapticHandler(); @@ -84,6 +87,13 @@ public class SDLControllerManager mHapticHandler.run(device_id, intensity, length); } + /** + * This method is called by SDL using JNI. + */ + public static void hapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + mHapticHandler.rumble(device_id, low_frequency_intensity, high_frequency_intensity, length); + } + /** * This method is called by SDL using JNI. */ @@ -233,10 +243,19 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler { } } + boolean can_rumble = false; + if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) { + VibratorManager manager = joystickDevice.getVibratorManager(); + int[] vibrators = manager.getVibratorIds(); + if (vibrators.length > 0) { + can_rumble = true; + } + } + mJoysticks.add(joystick); SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), - getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2); + getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, can_rumble); } } } @@ -470,12 +489,63 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { } } +class SDLHapticHandler_API31 extends SDLHapticHandler { + @Override + public void run(int device_id, float intensity, int length) { + SDLHaptic haptic = getHaptic(device_id); + if (haptic != null) { + vibrate(haptic.vib, intensity, length); + } + } + + @Override + public void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + InputDevice device = InputDevice.getDevice(device_id); + if (device == null) { + return; + } + + VibratorManager manager = device.getVibratorManager(); + int[] vibrators = manager.getVibratorIds(); + if (vibrators.length >= 2) { + vibrate(manager.getVibrator(vibrators[0]), low_frequency_intensity, length); + vibrate(manager.getVibrator(vibrators[1]), high_frequency_intensity, length); + } else if (vibrators.length == 1) { + float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f); + vibrate(manager.getVibrator(vibrators[0]), intensity, length); + } + } + + private void vibrate(Vibrator vibrator, float intensity, int length) { + if (intensity == 0.0f) { + vibrator.cancel(); + return; + } + + int value = Math.round(intensity * 255); + if (value > 255) { + value = 255; + } + if (value < 1) { + vibrator.cancel(); + return; + } + try { + vibrator.vibrate(VibrationEffect.createOneShot(length, value)); + } + catch (Exception e) { + // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if + // something went horribly wrong with the Android 8.0 APIs. + vibrator.vibrate(length); + } + } +} + class SDLHapticHandler_API26 extends SDLHapticHandler { @Override public void run(int device_id, float intensity, int length) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { - Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length); if (intensity == 0.0f) { stop(device_id); return; @@ -523,6 +593,10 @@ class SDLHapticHandler { } } + public void rumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) { + // Not supported in older APIs + } + public void stop(int device_id) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { @@ -535,30 +609,6 @@ class SDLHapticHandler { final int deviceId_VIBRATOR_SERVICE = 999999; boolean hasVibratorService = false; - int[] deviceIds = InputDevice.getDeviceIds(); - // It helps processing the device ids in reverse order - // For example, in the case of the XBox 360 wireless dongle, - // so the first controller seen by SDL matches what the receiver - // considers to be the first controller - - for (int i = deviceIds.length - 1; i > -1; i--) { - SDLHaptic haptic = getHaptic(deviceIds[i]); - if (haptic == null) { - InputDevice device = InputDevice.getDevice(deviceIds[i]); - Vibrator vib = device.getVibrator(); - if (vib != null) { - if (vib.hasVibrator()) { - haptic = new SDLHaptic(); - haptic.device_id = deviceIds[i]; - haptic.name = device.getName(); - haptic.vib = vib; - mHaptics.add(haptic); - SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); - } - } - } - } - /* Check VIBRATOR_SERVICE */ Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vib != null) { @@ -581,18 +631,11 @@ class SDLHapticHandler { ArrayList removedDevices = null; for (SDLHaptic haptic : mHaptics) { int device_id = haptic.device_id; - int i; - for (i = 0; i < deviceIds.length; i++) { - if (device_id == deviceIds[i]) break; - } - if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) { - if (i == deviceIds.length) { - if (removedDevices == null) { - removedDevices = new ArrayList(); - } - removedDevices.add(device_id); + if (removedDevices == null) { + removedDevices = new ArrayList(); } + removedDevices.add(device_id); } // else: don't remove the vibrator if it is still present } diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 484aec94f..70d509654 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -274,7 +274,7 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)( JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats); + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble); JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)( JNIEnv *env, jclass jcls, @@ -294,7 +294,7 @@ static JNINativeMethod SDLControllerManager_tab[] = { { "onNativePadUp", "(II)I", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) }, { "onNativeJoy", "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) }, { "onNativeHat", "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) }, - { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIII)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, + { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZ)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) }, { "nativeRemoveJoystick", "(I)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) }, { "nativeAddHaptic", "(ILjava/lang/String;)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) }, { "nativeRemoveHaptic", "(I)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) } @@ -378,6 +378,7 @@ static jclass mControllerManagerClass; static jmethodID midPollInputDevices; static jmethodID midPollHapticDevices; static jmethodID midHapticRun; +static jmethodID midHapticRumble; static jmethodID midHapticStop; /* Accelerometer data storage */ @@ -746,10 +747,12 @@ JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env "pollHapticDevices", "()V"); midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass, "hapticRun", "(IFI)V"); + midHapticRumble = (*env)->GetStaticMethodID(env, mControllerManagerClass, + "hapticRumble", "(IFFI)V"); midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass, "hapticStop", "(I)V"); - if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticStop) { + if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) { __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?"); } @@ -1069,13 +1072,13 @@ JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)( JNIEnv *env, jclass jcls, jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id, - jint button_mask, jint naxes, jint axis_mask, jint nhats) + jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble) { int retval; const char *name = (*env)->GetStringUTFChars(env, device_name, NULL); const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL); - retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats); + retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble); (*env)->ReleaseStringUTFChars(env, device_name, name); (*env)->ReleaseStringUTFChars(env, device_desc, desc); @@ -2201,6 +2204,12 @@ void Android_JNI_HapticRun(int device_id, float intensity, int length) (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length); } +void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length) +{ + JNIEnv *env = Android_JNI_GetEnv(); + (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRumble, device_id, low_frequency_intensity, high_frequency_intensity, length); +} + void Android_JNI_HapticStop(int device_id) { JNIEnv *env = Android_JNI_GetEnv(); diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index 71704203e..069199e99 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -91,6 +91,7 @@ void Android_JNI_PollInputDevices(void); /* Haptic support */ void Android_JNI_PollHapticDevices(void); void Android_JNI_HapticRun(int device_id, float intensity, int length); +void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length); void Android_JNI_HapticStop(int device_id); /* Video */ diff --git a/src/haptic/android/SDL_syshaptic.c b/src/haptic/android/SDL_syshaptic.c index 3f35004b4..0ecb0a051 100644 --- a/src/haptic/android/SDL_syshaptic.c +++ b/src/haptic/android/SDL_syshaptic.c @@ -25,8 +25,6 @@ #include "SDL_syshaptic_c.h" #include "../SDL_syshaptic.h" #include "../../core/android/SDL_android.h" -#include "../../joystick/SDL_sysjoystick.h" /* For the real SDL_Joystick */ -#include "../../joystick/android/SDL_sysjoystick_c.h" /* For joystick hwdata */ typedef struct SDL_hapticlist_item { @@ -78,18 +76,6 @@ static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id) return NULL; } -static SDL_hapticlist_item *HapticByDevId(int device_id) -{ - SDL_hapticlist_item *item; - for (item = SDL_hapticlist; item; item = item->next) { - if (device_id == item->device_id) { - /*SDL_Log("=+=+=+=+=+= HapticByDevId id [%d]", device_id);*/ - return item; - } - } - return NULL; -} - SDL_HapticID SDL_SYS_HapticInstanceID(int index) { SDL_hapticlist_item *item = HapticByOrder(index); @@ -142,11 +128,6 @@ static SDL_hapticlist_item *OpenHapticByInstanceID(SDL_Haptic *haptic, SDL_Hapti return OpenHaptic(haptic, HapticByInstanceID(instance_id)); } -static SDL_hapticlist_item *OpenHapticByDevId(SDL_Haptic *haptic, int device_id) -{ - return OpenHaptic(haptic, HapticByDevId(device_id)); -} - int SDL_SYS_HapticOpen(SDL_Haptic *haptic) { return OpenHapticByInstanceID(haptic, haptic->instance_id) == NULL ? -1 : 0; @@ -159,19 +140,17 @@ int SDL_SYS_HapticMouse(void) int SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick) { - SDL_hapticlist_item *item; - item = HapticByDevId(((joystick_hwdata *)joystick->hwdata)->device_id); - return (item) ? 1 : 0; + return 0; } int SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick) { - return OpenHapticByDevId(haptic, ((joystick_hwdata *)joystick->hwdata)->device_id) == NULL ? -1 : 0; + return SDL_Unsupported(); } int SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick) { - return ((SDL_hapticlist_item *)haptic->hwdata)->device_id == ((joystick_hwdata *)joystick->hwdata)->device_id ? 1 : 0; + return 0; } void SDL_SYS_HapticClose(SDL_Haptic *haptic) diff --git a/src/joystick/android/SDL_sysjoystick.c b/src/joystick/android/SDL_sysjoystick.c index 117077424..73ab60409 100644 --- a/src/joystick/android/SDL_sysjoystick.c +++ b/src/joystick/android/SDL_sysjoystick.c @@ -301,7 +301,7 @@ int Android_OnHat(int device_id, int hat_id, int x, int y) return -1; } -int Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats) +int Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, SDL_bool can_rumble) { SDL_joylist_item *item; SDL_JoystickGUID guid; @@ -372,6 +372,7 @@ int Android_AddJoystick(int device_id, const char *name, const char *desc, int v } item->naxes = naxes; item->nhats = nhats; + item->can_rumble = can_rumble; item->device_instance = SDL_GetNextObjectID(); if (!SDL_joylist_tail) { SDL_joylist = SDL_joylist_tail = item; @@ -578,12 +579,24 @@ static int ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index) joystick->nbuttons = item->nbuttons; joystick->naxes = item->naxes; + if (item->can_rumble) { + SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, SDL_TRUE); + } + return 0; } static int ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - return SDL_Unsupported(); + SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; + if (!item->can_rumble) { + return SDL_Unsupported(); + } + + float low_frequency_intensity = (float)low_frequency_rumble / SDL_MAX_UINT16; + float high_frequency_intensity = (float)high_frequency_rumble / SDL_MAX_UINT16; + Android_JNI_HapticRumble(item->device_id, low_frequency_intensity, high_frequency_intensity, 5000); + return 0; } static int ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) diff --git a/src/joystick/android/SDL_sysjoystick_c.h b/src/joystick/android/SDL_sysjoystick_c.h index 06424887a..363991199 100644 --- a/src/joystick/android/SDL_sysjoystick_c.h +++ b/src/joystick/android/SDL_sysjoystick_c.h @@ -32,7 +32,7 @@ extern int Android_OnPadDown(int device_id, int keycode); extern int Android_OnPadUp(int device_id, int keycode); extern int Android_OnJoy(int device_id, int axisnum, float value); extern int Android_OnHat(int device_id, int hat_id, int x, int y); -extern int Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats); +extern int Android_AddJoystick(int device_id, const char *name, const char *desc, int vendor_id, int product_id, int button_mask, int naxes, int axis_mask, int nhats, SDL_bool can_rumble); extern int Android_RemoveJoystick(int device_id); /* A linked list of available joysticks */ @@ -45,6 +45,7 @@ typedef struct SDL_joylist_item SDL_Joystick *joystick; int nbuttons, naxes, nhats; int dpad_state; + SDL_bool can_rumble; struct SDL_joylist_item *next; } SDL_joylist_item;