1 package org.libsdl.app;
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
8 import android.content.Context;
9 import android.os.Build;
10 import android.os.VibrationEffect;
11 import android.os.Vibrator;
12 import android.util.Log;
13 import android.view.InputDevice;
14 import android.view.KeyEvent;
15 import android.view.MotionEvent;
16 import android.view.View;
19 public class SDLControllerManager
22 public static native int nativeSetupJNI();
24 public static native int nativeAddJoystick(int device_id, String name, String desc,
25 int vendor_id, int product_id,
26 boolean is_accelerometer, int button_mask,
27 int naxes, int nhats, int nballs);
28 public static native int nativeRemoveJoystick(int device_id);
29 public static native int nativeAddHaptic(int device_id, String name);
30 public static native int nativeRemoveHaptic(int device_id);
31 public static native int onNativePadDown(int device_id, int keycode);
32 public static native int onNativePadUp(int device_id, int keycode);
33 public static native void onNativeJoy(int device_id, int axis,
35 public static native void onNativeHat(int device_id, int hat_id,
38 protected static SDLJoystickHandler mJoystickHandler;
39 protected static SDLHapticHandler mHapticHandler;
41 private static final String TAG = "SDLControllerManager";
43 public static void initialize() {
44 if (mJoystickHandler == null) {
45 if (Build.VERSION.SDK_INT >= 19) {
46 mJoystickHandler = new SDLJoystickHandler_API19();
48 mJoystickHandler = new SDLJoystickHandler_API16();
52 if (mHapticHandler == null) {
53 if (Build.VERSION.SDK_INT >= 26) {
54 mHapticHandler = new SDLHapticHandler_API26();
56 mHapticHandler = new SDLHapticHandler();
61 // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
62 public static boolean handleJoystickMotionEvent(MotionEvent event) {
63 return mJoystickHandler.handleMotionEvent(event);
67 * This method is called by SDL using JNI.
69 public static void pollInputDevices() {
70 mJoystickHandler.pollInputDevices();
74 * This method is called by SDL using JNI.
76 public static void pollHapticDevices() {
77 mHapticHandler.pollHapticDevices();
81 * This method is called by SDL using JNI.
83 public static void hapticRun(int device_id, float intensity, int length) {
84 mHapticHandler.run(device_id, intensity, length);
88 * This method is called by SDL using JNI.
90 public static void hapticStop(int device_id)
92 mHapticHandler.stop(device_id);
95 // Check if a given device is considered a possible SDL joystick
96 public static boolean isDeviceSDLJoystick(int deviceId) {
97 InputDevice device = InputDevice.getDevice(deviceId);
98 // We cannot use InputDevice.isVirtual before API 16, so let's accept
99 // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
100 if ((device == null) || (deviceId < 0)) {
103 int sources = device.getSources();
105 /* This is called for every button press, so let's not spam the logs */
107 if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
108 Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
110 if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
111 Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
113 if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
114 Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
118 return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
119 ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
120 ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
126 class SDLJoystickHandler {
129 * Handles given MotionEvent.
130 * @param event the event to be handled.
131 * @return if given event was processed.
133 public boolean handleMotionEvent(MotionEvent event) {
138 * Handles adding and removing of input devices.
140 public void pollInputDevices() {
144 /* Actual joystick functionality available for API >= 12 devices */
145 class SDLJoystickHandler_API16 extends SDLJoystickHandler {
147 static class SDLJoystick {
148 public int device_id;
151 public ArrayList<InputDevice.MotionRange> axes;
152 public ArrayList<InputDevice.MotionRange> hats;
154 static class RangeComparator implements Comparator<InputDevice.MotionRange> {
156 public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
157 // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
158 int arg0Axis = arg0.getAxis();
159 int arg1Axis = arg1.getAxis();
160 if (arg0Axis == MotionEvent.AXIS_GAS) {
161 arg0Axis = MotionEvent.AXIS_BRAKE;
162 } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
163 arg0Axis = MotionEvent.AXIS_GAS;
165 if (arg1Axis == MotionEvent.AXIS_GAS) {
166 arg1Axis = MotionEvent.AXIS_BRAKE;
167 } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
168 arg1Axis = MotionEvent.AXIS_GAS;
171 return arg0Axis - arg1Axis;
175 private final ArrayList<SDLJoystick> mJoysticks;
177 public SDLJoystickHandler_API16() {
179 mJoysticks = new ArrayList<SDLJoystick>();
183 public void pollInputDevices() {
184 int[] deviceIds = InputDevice.getDeviceIds();
186 for (int device_id : deviceIds) {
187 if (SDLControllerManager.isDeviceSDLJoystick(device_id)) {
188 SDLJoystick joystick = getJoystick(device_id);
189 if (joystick == null) {
190 InputDevice joystickDevice = InputDevice.getDevice(device_id);
191 joystick = new SDLJoystick();
192 joystick.device_id = device_id;
193 joystick.name = joystickDevice.getName();
194 joystick.desc = getJoystickDescriptor(joystickDevice);
195 joystick.axes = new ArrayList<InputDevice.MotionRange>();
196 joystick.hats = new ArrayList<InputDevice.MotionRange>();
198 List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
199 Collections.sort(ranges, new RangeComparator());
200 for (InputDevice.MotionRange range : ranges) {
201 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
202 if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
203 joystick.hats.add(range);
205 joystick.axes.add(range);
210 mJoysticks.add(joystick);
211 SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
212 getVendorId(joystickDevice), getProductId(joystickDevice), false,
213 getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
218 /* Check removed devices */
219 ArrayList<Integer> removedDevices = null;
220 for (SDLJoystick joystick : mJoysticks) {
221 int device_id = joystick.device_id;
223 for (i = 0; i < deviceIds.length; i++) {
224 if (device_id == deviceIds[i]) break;
226 if (i == deviceIds.length) {
227 if (removedDevices == null) {
228 removedDevices = new ArrayList<Integer>();
230 removedDevices.add(device_id);
234 if (removedDevices != null) {
235 for (int device_id : removedDevices) {
236 SDLControllerManager.nativeRemoveJoystick(device_id);
237 for (int i = 0; i < mJoysticks.size(); i++) {
238 if (mJoysticks.get(i).device_id == device_id) {
239 mJoysticks.remove(i);
247 protected SDLJoystick getJoystick(int device_id) {
248 for (SDLJoystick joystick : mJoysticks) {
249 if (joystick.device_id == device_id) {
257 public boolean handleMotionEvent(MotionEvent event) {
258 if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
259 int actionPointerIndex = event.getActionIndex();
260 int action = event.getActionMasked();
261 if (action == MotionEvent.ACTION_MOVE) {
262 SDLJoystick joystick = getJoystick(event.getDeviceId());
263 if (joystick != null) {
264 for (int i = 0; i < joystick.axes.size(); i++) {
265 InputDevice.MotionRange range = joystick.axes.get(i);
266 /* Normalize the value to -1...1 */
267 float value = (event.getAxisValue(range.getAxis(), actionPointerIndex) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
268 SDLControllerManager.onNativeJoy(joystick.device_id, i, value);
270 for (int i = 0; i < joystick.hats.size() / 2; i++) {
271 int hatX = Math.round(event.getAxisValue(joystick.hats.get(2 * i).getAxis(), actionPointerIndex));
272 int hatY = Math.round(event.getAxisValue(joystick.hats.get(2 * i + 1).getAxis(), actionPointerIndex));
273 SDLControllerManager.onNativeHat(joystick.device_id, i, hatX, hatY);
281 public String getJoystickDescriptor(InputDevice joystickDevice) {
282 String desc = joystickDevice.getDescriptor();
284 if (desc != null && !desc.isEmpty()) {
288 return joystickDevice.getName();
290 public int getProductId(InputDevice joystickDevice) {
293 public int getVendorId(InputDevice joystickDevice) {
296 public int getButtonMask(InputDevice joystickDevice) {
301 class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
304 public int getProductId(InputDevice joystickDevice) {
305 return joystickDevice.getProductId();
309 public int getVendorId(InputDevice joystickDevice) {
310 return joystickDevice.getVendorId();
314 public int getButtonMask(InputDevice joystickDevice) {
316 int[] keys = new int[] {
317 KeyEvent.KEYCODE_BUTTON_A,
318 KeyEvent.KEYCODE_BUTTON_B,
319 KeyEvent.KEYCODE_BUTTON_X,
320 KeyEvent.KEYCODE_BUTTON_Y,
321 KeyEvent.KEYCODE_BACK,
322 KeyEvent.KEYCODE_BUTTON_MODE,
323 KeyEvent.KEYCODE_BUTTON_START,
324 KeyEvent.KEYCODE_BUTTON_THUMBL,
325 KeyEvent.KEYCODE_BUTTON_THUMBR,
326 KeyEvent.KEYCODE_BUTTON_L1,
327 KeyEvent.KEYCODE_BUTTON_R1,
328 KeyEvent.KEYCODE_DPAD_UP,
329 KeyEvent.KEYCODE_DPAD_DOWN,
330 KeyEvent.KEYCODE_DPAD_LEFT,
331 KeyEvent.KEYCODE_DPAD_RIGHT,
332 KeyEvent.KEYCODE_BUTTON_SELECT,
333 KeyEvent.KEYCODE_DPAD_CENTER,
335 // These don't map into any SDL controller buttons directly
336 KeyEvent.KEYCODE_BUTTON_L2,
337 KeyEvent.KEYCODE_BUTTON_R2,
338 KeyEvent.KEYCODE_BUTTON_C,
339 KeyEvent.KEYCODE_BUTTON_Z,
340 KeyEvent.KEYCODE_BUTTON_1,
341 KeyEvent.KEYCODE_BUTTON_2,
342 KeyEvent.KEYCODE_BUTTON_3,
343 KeyEvent.KEYCODE_BUTTON_4,
344 KeyEvent.KEYCODE_BUTTON_5,
345 KeyEvent.KEYCODE_BUTTON_6,
346 KeyEvent.KEYCODE_BUTTON_7,
347 KeyEvent.KEYCODE_BUTTON_8,
348 KeyEvent.KEYCODE_BUTTON_9,
349 KeyEvent.KEYCODE_BUTTON_10,
350 KeyEvent.KEYCODE_BUTTON_11,
351 KeyEvent.KEYCODE_BUTTON_12,
352 KeyEvent.KEYCODE_BUTTON_13,
353 KeyEvent.KEYCODE_BUTTON_14,
354 KeyEvent.KEYCODE_BUTTON_15,
355 KeyEvent.KEYCODE_BUTTON_16,
357 int[] masks = new int[] {
362 (1 << 4), // BACK -> BACK
363 (1 << 5), // MODE -> GUIDE
364 (1 << 6), // START -> START
365 (1 << 7), // THUMBL -> LEFTSTICK
366 (1 << 8), // THUMBR -> RIGHTSTICK
367 (1 << 9), // L1 -> LEFTSHOULDER
368 (1 << 10), // R1 -> RIGHTSHOULDER
369 (1 << 11), // DPAD_UP -> DPAD_UP
370 (1 << 12), // DPAD_DOWN -> DPAD_DOWN
371 (1 << 13), // DPAD_LEFT -> DPAD_LEFT
372 (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
373 (1 << 4), // SELECT -> BACK
374 (1 << 0), // DPAD_CENTER -> A
375 (1 << 15), // L2 -> ??
376 (1 << 16), // R2 -> ??
377 (1 << 17), // C -> ??
378 (1 << 18), // Z -> ??
379 (1 << 20), // 1 -> ??
380 (1 << 21), // 2 -> ??
381 (1 << 22), // 3 -> ??
382 (1 << 23), // 4 -> ??
383 (1 << 24), // 5 -> ??
384 (1 << 25), // 6 -> ??
385 (1 << 26), // 7 -> ??
386 (1 << 27), // 8 -> ??
387 (1 << 28), // 9 -> ??
388 (1 << 29), // 10 -> ??
389 (1 << 30), // 11 -> ??
390 (1 << 31), // 12 -> ??
391 // We're out of room...
392 0xFFFFFFFF, // 13 -> ??
393 0xFFFFFFFF, // 14 -> ??
394 0xFFFFFFFF, // 15 -> ??
395 0xFFFFFFFF, // 16 -> ??
397 boolean[] has_keys = joystickDevice.hasKeys(keys);
398 for (int i = 0; i < keys.length; ++i) {
400 button_mask |= masks[i];
407 class SDLHapticHandler_API26 extends SDLHapticHandler {
409 public void run(int device_id, float intensity, int length) {
410 SDLHaptic haptic = getHaptic(device_id);
411 if (haptic != null) {
412 Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
413 if (intensity == 0.0f) {
418 int vibeValue = Math.round(intensity * 255);
420 if (vibeValue > 255) {
428 haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
430 catch (Exception e) {
431 // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
432 // something went horribly wrong with the Android 8.0 APIs.
433 haptic.vib.vibrate(length);
439 class SDLHapticHandler {
441 static class SDLHaptic {
442 public int device_id;
447 private final ArrayList<SDLHaptic> mHaptics;
449 public SDLHapticHandler() {
450 mHaptics = new ArrayList<SDLHaptic>();
453 public void run(int device_id, float intensity, int length) {
454 SDLHaptic haptic = getHaptic(device_id);
455 if (haptic != null) {
456 haptic.vib.vibrate(length);
460 public void stop(int device_id) {
461 SDLHaptic haptic = getHaptic(device_id);
462 if (haptic != null) {
467 public void pollHapticDevices() {
469 final int deviceId_VIBRATOR_SERVICE = 999999;
470 boolean hasVibratorService = false;
472 int[] deviceIds = InputDevice.getDeviceIds();
473 // It helps processing the device ids in reverse order
474 // For example, in the case of the XBox 360 wireless dongle,
475 // so the first controller seen by SDL matches what the receiver
476 // considers to be the first controller
478 for (int i = deviceIds.length - 1; i > -1; i--) {
479 SDLHaptic haptic = getHaptic(deviceIds[i]);
480 if (haptic == null) {
481 InputDevice device = InputDevice.getDevice(deviceIds[i]);
482 Vibrator vib = device.getVibrator();
483 if (vib.hasVibrator()) {
484 haptic = new SDLHaptic();
485 haptic.device_id = deviceIds[i];
486 haptic.name = device.getName();
488 mHaptics.add(haptic);
489 SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
494 /* Check VIBRATOR_SERVICE */
495 Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
497 hasVibratorService = vib.hasVibrator();
499 if (hasVibratorService) {
500 SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
501 if (haptic == null) {
502 haptic = new SDLHaptic();
503 haptic.device_id = deviceId_VIBRATOR_SERVICE;
504 haptic.name = "VIBRATOR_SERVICE";
506 mHaptics.add(haptic);
507 SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
512 /* Check removed devices */
513 ArrayList<Integer> removedDevices = null;
514 for (SDLHaptic haptic : mHaptics) {
515 int device_id = haptic.device_id;
517 for (i = 0; i < deviceIds.length; i++) {
518 if (device_id == deviceIds[i]) break;
521 if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
522 if (i == deviceIds.length) {
523 if (removedDevices == null) {
524 removedDevices = new ArrayList<Integer>();
526 removedDevices.add(device_id);
528 } // else: don't remove the vibrator if it is still present
531 if (removedDevices != null) {
532 for (int device_id : removedDevices) {
533 SDLControllerManager.nativeRemoveHaptic(device_id);
534 for (int i = 0; i < mHaptics.size(); i++) {
535 if (mHaptics.get(i).device_id == device_id) {
544 protected SDLHaptic getHaptic(int device_id) {
545 for (SDLHaptic haptic : mHaptics) {
546 if (haptic.device_id == device_id) {
554 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
555 // Generic Motion (mouse hover, joystick...) events go here
557 public boolean onGenericMotion(View v, MotionEvent event) {
561 switch ( event.getSource() ) {
562 case InputDevice.SOURCE_JOYSTICK:
563 case InputDevice.SOURCE_GAMEPAD:
564 case InputDevice.SOURCE_DPAD:
565 return SDLControllerManager.handleJoystickMotionEvent(event);
567 case InputDevice.SOURCE_MOUSE:
568 action = event.getActionMasked();
570 case MotionEvent.ACTION_SCROLL:
571 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
572 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
573 SDLActivity.onNativeMouse(0, action, x, y, false);
576 case MotionEvent.ACTION_HOVER_MOVE:
580 SDLActivity.onNativeMouse(0, action, x, y, false);
592 // Event was not managed
596 public boolean supportsRelativeMouse() {
600 public boolean inRelativeMode() {
604 public boolean setRelativeMouseEnabled(boolean enabled) {
608 public void reclaimRelativeMouseModeIfNeeded()
613 public float getEventX(MotionEvent event) {
614 return event.getX(0);
617 public float getEventY(MotionEvent event) {
618 return event.getY(0);
623 class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
624 // Generic Motion (mouse hover, joystick...) events go here
626 private boolean mRelativeModeEnabled;
629 public boolean onGenericMotion(View v, MotionEvent event) {
631 // Handle relative mouse mode
632 if (mRelativeModeEnabled) {
633 if (event.getSource() == InputDevice.SOURCE_MOUSE) {
634 int action = event.getActionMasked();
635 if (action == MotionEvent.ACTION_HOVER_MOVE) {
636 float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
637 float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
638 SDLActivity.onNativeMouse(0, action, x, y, true);
644 // Event was not managed, call SDLGenericMotionListener_API12 method
645 return super.onGenericMotion(v, event);
649 public boolean supportsRelativeMouse() {
654 public boolean inRelativeMode() {
655 return mRelativeModeEnabled;
659 public boolean setRelativeMouseEnabled(boolean enabled) {
660 mRelativeModeEnabled = enabled;
665 public float getEventX(MotionEvent event) {
666 if (mRelativeModeEnabled) {
667 return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
669 return event.getX(0);
674 public float getEventY(MotionEvent event) {
675 if (mRelativeModeEnabled) {
676 return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
678 return event.getY(0);
683 class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
684 // Generic Motion (mouse hover, joystick...) events go here
685 private boolean mRelativeModeEnabled;
688 public boolean onGenericMotion(View v, MotionEvent event) {
692 switch ( event.getSource() ) {
693 case InputDevice.SOURCE_JOYSTICK:
694 case InputDevice.SOURCE_GAMEPAD:
695 case InputDevice.SOURCE_DPAD:
696 return SDLControllerManager.handleJoystickMotionEvent(event);
698 case InputDevice.SOURCE_MOUSE:
699 // DeX desktop mouse cursor is a separate non-standard input type.
700 case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
701 action = event.getActionMasked();
703 case MotionEvent.ACTION_SCROLL:
704 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
705 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
706 SDLActivity.onNativeMouse(0, action, x, y, false);
709 case MotionEvent.ACTION_HOVER_MOVE:
712 SDLActivity.onNativeMouse(0, action, x, y, false);
720 case InputDevice.SOURCE_MOUSE_RELATIVE:
721 action = event.getActionMasked();
723 case MotionEvent.ACTION_SCROLL:
724 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
725 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
726 SDLActivity.onNativeMouse(0, action, x, y, false);
729 case MotionEvent.ACTION_HOVER_MOVE:
732 SDLActivity.onNativeMouse(0, action, x, y, true);
744 // Event was not managed
749 public boolean supportsRelativeMouse() {
750 return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
754 public boolean inRelativeMode() {
755 return mRelativeModeEnabled;
759 public boolean setRelativeMouseEnabled(boolean enabled) {
760 if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
762 SDLActivity.getContentView().requestPointerCapture();
764 SDLActivity.getContentView().releasePointerCapture();
766 mRelativeModeEnabled = enabled;
774 public void reclaimRelativeMouseModeIfNeeded()
776 if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
777 SDLActivity.getContentView().requestPointerCapture();
782 public float getEventX(MotionEvent event) {
783 // Relative mouse in capture mode will only have relative for X/Y
784 return event.getX(0);
788 public float getEventY(MotionEvent event) {
789 // Relative mouse in capture mode will only have relative for X/Y
790 return event.getY(0);