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;
10 import android.view.*;
11 import android.util.Log;
14 public class SDLControllerManager
17 public static native int nativeSetupJNI();
19 public static native int nativeAddJoystick(int device_id, String name, String desc,
20 int vendor_id, int product_id,
21 boolean is_accelerometer, int button_mask,
22 int naxes, int nhats, int nballs);
23 public static native int nativeRemoveJoystick(int device_id);
24 public static native int nativeAddHaptic(int device_id, String name);
25 public static native int nativeRemoveHaptic(int device_id);
26 public static native int onNativePadDown(int device_id, int keycode);
27 public static native int onNativePadUp(int device_id, int keycode);
28 public static native void onNativeJoy(int device_id, int axis,
30 public static native void onNativeHat(int device_id, int hat_id,
33 protected static SDLJoystickHandler mJoystickHandler;
34 protected static SDLHapticHandler mHapticHandler;
36 private static final String TAG = "SDLControllerManager";
38 public static void initialize() {
39 if (mJoystickHandler == null) {
40 if (Build.VERSION.SDK_INT >= 19) {
41 mJoystickHandler = new SDLJoystickHandler_API19();
43 mJoystickHandler = new SDLJoystickHandler_API16();
47 if (mHapticHandler == null) {
48 if (Build.VERSION.SDK_INT >= 26) {
49 mHapticHandler = new SDLHapticHandler_API26();
51 mHapticHandler = new SDLHapticHandler();
56 // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
57 public static boolean handleJoystickMotionEvent(MotionEvent event) {
58 return mJoystickHandler.handleMotionEvent(event);
62 * This method is called by SDL using JNI.
64 public static void pollInputDevices() {
65 mJoystickHandler.pollInputDevices();
69 * This method is called by SDL using JNI.
71 public static void pollHapticDevices() {
72 mHapticHandler.pollHapticDevices();
76 * This method is called by SDL using JNI.
78 public static void hapticRun(int device_id, float intensity, int length) {
79 mHapticHandler.run(device_id, intensity, length);
83 * This method is called by SDL using JNI.
85 public static void hapticStop(int device_id)
87 mHapticHandler.stop(device_id);
90 // Check if a given device is considered a possible SDL joystick
91 public static boolean isDeviceSDLJoystick(int deviceId) {
92 InputDevice device = InputDevice.getDevice(deviceId);
93 // We cannot use InputDevice.isVirtual before API 16, so let's accept
94 // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
95 if ((device == null) || (deviceId < 0)) {
98 int sources = device.getSources();
100 /* This is called for every button press, so let's not spam the logs */
102 if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
103 Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
105 if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
106 Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
108 if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
109 Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
113 return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
114 ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
115 ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
121 class SDLJoystickHandler {
124 * Handles given MotionEvent.
125 * @param event the event to be handled.
126 * @return if given event was processed.
128 public boolean handleMotionEvent(MotionEvent event) {
133 * Handles adding and removing of input devices.
135 public void pollInputDevices() {
139 /* Actual joystick functionality available for API >= 12 devices */
140 class SDLJoystickHandler_API16 extends SDLJoystickHandler {
142 static class SDLJoystick {
143 public int device_id;
146 public ArrayList<InputDevice.MotionRange> axes;
147 public ArrayList<InputDevice.MotionRange> hats;
149 static class RangeComparator implements Comparator<InputDevice.MotionRange> {
151 public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
152 // 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
153 int arg0Axis = arg0.getAxis();
154 int arg1Axis = arg1.getAxis();
155 if (arg0Axis == MotionEvent.AXIS_GAS) {
156 arg0Axis = MotionEvent.AXIS_BRAKE;
157 } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
158 arg0Axis = MotionEvent.AXIS_GAS;
160 if (arg1Axis == MotionEvent.AXIS_GAS) {
161 arg1Axis = MotionEvent.AXIS_BRAKE;
162 } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
163 arg1Axis = MotionEvent.AXIS_GAS;
166 return arg0Axis - arg1Axis;
170 private ArrayList<SDLJoystick> mJoysticks;
172 public SDLJoystickHandler_API16() {
174 mJoysticks = new ArrayList<SDLJoystick>();
178 public void pollInputDevices() {
179 int[] deviceIds = InputDevice.getDeviceIds();
180 for(int i=0; i < deviceIds.length; ++i) {
181 SDLJoystick joystick = getJoystick(deviceIds[i]);
182 if (joystick == null) {
183 joystick = new SDLJoystick();
184 InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
185 if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
186 joystick.device_id = deviceIds[i];
187 joystick.name = joystickDevice.getName();
188 joystick.desc = getJoystickDescriptor(joystickDevice);
189 joystick.axes = new ArrayList<InputDevice.MotionRange>();
190 joystick.hats = new ArrayList<InputDevice.MotionRange>();
192 List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
193 Collections.sort(ranges, new RangeComparator());
194 for (InputDevice.MotionRange range : ranges ) {
195 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
196 if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
197 range.getAxis() == MotionEvent.AXIS_HAT_Y) {
198 joystick.hats.add(range);
201 joystick.axes.add(range);
206 mJoysticks.add(joystick);
207 SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), false, getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
212 /* Check removed devices */
213 ArrayList<Integer> removedDevices = new ArrayList<Integer>();
214 for(int i=0; i < mJoysticks.size(); i++) {
215 int device_id = mJoysticks.get(i).device_id;
217 for (j=0; j < deviceIds.length; j++) {
218 if (device_id == deviceIds[j]) break;
220 if (j == deviceIds.length) {
221 removedDevices.add(Integer.valueOf(device_id));
225 for(int i=0; i < removedDevices.size(); i++) {
226 int device_id = removedDevices.get(i).intValue();
227 SDLControllerManager.nativeRemoveJoystick(device_id);
228 for (int j=0; j < mJoysticks.size(); j++) {
229 if (mJoysticks.get(j).device_id == device_id) {
230 mJoysticks.remove(j);
237 protected SDLJoystick getJoystick(int device_id) {
238 for(int i=0; i < mJoysticks.size(); i++) {
239 if (mJoysticks.get(i).device_id == device_id) {
240 return mJoysticks.get(i);
247 public boolean handleMotionEvent(MotionEvent event) {
248 if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
249 int actionPointerIndex = event.getActionIndex();
250 int action = event.getActionMasked();
252 case MotionEvent.ACTION_MOVE:
253 SDLJoystick joystick = getJoystick(event.getDeviceId());
254 if ( joystick != null ) {
255 for (int i = 0; i < joystick.axes.size(); i++) {
256 InputDevice.MotionRange range = joystick.axes.get(i);
257 /* Normalize the value to -1...1 */
258 float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
259 SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
261 for (int i = 0; i < joystick.hats.size(); i+=2) {
262 int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
263 int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
264 SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
275 public String getJoystickDescriptor(InputDevice joystickDevice) {
276 String desc = joystickDevice.getDescriptor();
278 if (desc != null && !desc.isEmpty()) {
282 return joystickDevice.getName();
284 public int getProductId(InputDevice joystickDevice) {
287 public int getVendorId(InputDevice joystickDevice) {
290 public int getButtonMask(InputDevice joystickDevice) {
295 class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
298 public int getProductId(InputDevice joystickDevice) {
299 return joystickDevice.getProductId();
303 public int getVendorId(InputDevice joystickDevice) {
304 return joystickDevice.getVendorId();
308 public int getButtonMask(InputDevice joystickDevice) {
310 int[] keys = new int[] {
311 KeyEvent.KEYCODE_BUTTON_A,
312 KeyEvent.KEYCODE_BUTTON_B,
313 KeyEvent.KEYCODE_BUTTON_X,
314 KeyEvent.KEYCODE_BUTTON_Y,
315 KeyEvent.KEYCODE_BACK,
316 KeyEvent.KEYCODE_BUTTON_MODE,
317 KeyEvent.KEYCODE_BUTTON_START,
318 KeyEvent.KEYCODE_BUTTON_THUMBL,
319 KeyEvent.KEYCODE_BUTTON_THUMBR,
320 KeyEvent.KEYCODE_BUTTON_L1,
321 KeyEvent.KEYCODE_BUTTON_R1,
322 KeyEvent.KEYCODE_DPAD_UP,
323 KeyEvent.KEYCODE_DPAD_DOWN,
324 KeyEvent.KEYCODE_DPAD_LEFT,
325 KeyEvent.KEYCODE_DPAD_RIGHT,
326 KeyEvent.KEYCODE_BUTTON_SELECT,
327 KeyEvent.KEYCODE_DPAD_CENTER,
329 // These don't map into any SDL controller buttons directly
330 KeyEvent.KEYCODE_BUTTON_L2,
331 KeyEvent.KEYCODE_BUTTON_R2,
332 KeyEvent.KEYCODE_BUTTON_C,
333 KeyEvent.KEYCODE_BUTTON_Z,
334 KeyEvent.KEYCODE_BUTTON_1,
335 KeyEvent.KEYCODE_BUTTON_2,
336 KeyEvent.KEYCODE_BUTTON_3,
337 KeyEvent.KEYCODE_BUTTON_4,
338 KeyEvent.KEYCODE_BUTTON_5,
339 KeyEvent.KEYCODE_BUTTON_6,
340 KeyEvent.KEYCODE_BUTTON_7,
341 KeyEvent.KEYCODE_BUTTON_8,
342 KeyEvent.KEYCODE_BUTTON_9,
343 KeyEvent.KEYCODE_BUTTON_10,
344 KeyEvent.KEYCODE_BUTTON_11,
345 KeyEvent.KEYCODE_BUTTON_12,
346 KeyEvent.KEYCODE_BUTTON_13,
347 KeyEvent.KEYCODE_BUTTON_14,
348 KeyEvent.KEYCODE_BUTTON_15,
349 KeyEvent.KEYCODE_BUTTON_16,
351 int[] masks = new int[] {
356 (1 << 4), // BACK -> BACK
357 (1 << 5), // MODE -> GUIDE
358 (1 << 6), // START -> START
359 (1 << 7), // THUMBL -> LEFTSTICK
360 (1 << 8), // THUMBR -> RIGHTSTICK
361 (1 << 9), // L1 -> LEFTSHOULDER
362 (1 << 10), // R1 -> RIGHTSHOULDER
363 (1 << 11), // DPAD_UP -> DPAD_UP
364 (1 << 12), // DPAD_DOWN -> DPAD_DOWN
365 (1 << 13), // DPAD_LEFT -> DPAD_LEFT
366 (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
367 (1 << 4), // SELECT -> BACK
368 (1 << 0), // DPAD_CENTER -> A
369 (1 << 15), // L2 -> ??
370 (1 << 16), // R2 -> ??
371 (1 << 17), // C -> ??
372 (1 << 18), // Z -> ??
373 (1 << 20), // 1 -> ??
374 (1 << 21), // 2 -> ??
375 (1 << 22), // 3 -> ??
376 (1 << 23), // 4 -> ??
377 (1 << 24), // 5 -> ??
378 (1 << 25), // 6 -> ??
379 (1 << 26), // 7 -> ??
380 (1 << 27), // 8 -> ??
381 (1 << 28), // 9 -> ??
382 (1 << 29), // 10 -> ??
383 (1 << 30), // 11 -> ??
384 (1 << 31), // 12 -> ??
385 // We're out of room...
386 0xFFFFFFFF, // 13 -> ??
387 0xFFFFFFFF, // 14 -> ??
388 0xFFFFFFFF, // 15 -> ??
389 0xFFFFFFFF, // 16 -> ??
391 boolean[] has_keys = joystickDevice.hasKeys(keys);
392 for (int i = 0; i < keys.length; ++i) {
394 button_mask |= masks[i];
401 class SDLHapticHandler_API26 extends SDLHapticHandler {
403 public void run(int device_id, float intensity, int length) {
404 SDLHaptic haptic = getHaptic(device_id);
405 if (haptic != null) {
406 Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
407 if (intensity == 0.0f) {
412 int vibeValue = Math.round(intensity * 255);
414 if (vibeValue > 255) {
422 haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
424 catch (Exception e) {
425 // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
426 // something went horribly wrong with the Android 8.0 APIs.
427 haptic.vib.vibrate(length);
433 class SDLHapticHandler {
436 public int device_id;
441 private ArrayList<SDLHaptic> mHaptics;
443 public SDLHapticHandler() {
444 mHaptics = new ArrayList<SDLHaptic>();
447 public void run(int device_id, float intensity, int length) {
448 SDLHaptic haptic = getHaptic(device_id);
449 if (haptic != null) {
450 haptic.vib.vibrate(length);
454 public void stop(int device_id) {
455 SDLHaptic haptic = getHaptic(device_id);
456 if (haptic != null) {
461 public void pollHapticDevices() {
463 final int deviceId_VIBRATOR_SERVICE = 999999;
464 boolean hasVibratorService = false;
466 int[] deviceIds = InputDevice.getDeviceIds();
467 // It helps processing the device ids in reverse order
468 // For example, in the case of the XBox 360 wireless dongle,
469 // so the first controller seen by SDL matches what the receiver
470 // considers to be the first controller
472 for (int i = deviceIds.length - 1; i > -1; i--) {
473 SDLHaptic haptic = getHaptic(deviceIds[i]);
474 if (haptic == null) {
475 InputDevice device = InputDevice.getDevice(deviceIds[i]);
476 Vibrator vib = device.getVibrator();
477 if (vib.hasVibrator()) {
478 haptic = new SDLHaptic();
479 haptic.device_id = deviceIds[i];
480 haptic.name = device.getName();
482 mHaptics.add(haptic);
483 SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
488 /* Check VIBRATOR_SERVICE */
489 Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
491 hasVibratorService = vib.hasVibrator();
493 if (hasVibratorService) {
494 SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
495 if (haptic == null) {
496 haptic = new SDLHaptic();
497 haptic.device_id = deviceId_VIBRATOR_SERVICE;
498 haptic.name = "VIBRATOR_SERVICE";
500 mHaptics.add(haptic);
501 SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
506 /* Check removed devices */
507 ArrayList<Integer> removedDevices = new ArrayList<Integer>();
508 for(int i=0; i < mHaptics.size(); i++) {
509 int device_id = mHaptics.get(i).device_id;
511 for (j=0; j < deviceIds.length; j++) {
512 if (device_id == deviceIds[j]) break;
515 if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) {
516 // don't remove the vibrator if it is still present
517 } else if (j == deviceIds.length) {
518 removedDevices.add(device_id);
522 for(int i=0; i < removedDevices.size(); i++) {
523 int device_id = removedDevices.get(i);
524 SDLControllerManager.nativeRemoveHaptic(device_id);
525 for (int j=0; j < mHaptics.size(); j++) {
526 if (mHaptics.get(j).device_id == device_id) {
534 protected SDLHaptic getHaptic(int device_id) {
535 for(int i=0; i < mHaptics.size(); i++) {
536 if (mHaptics.get(i).device_id == device_id) {
537 return mHaptics.get(i);
544 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
545 // Generic Motion (mouse hover, joystick...) events go here
547 public boolean onGenericMotion(View v, MotionEvent event) {
551 switch ( event.getSource() ) {
552 case InputDevice.SOURCE_JOYSTICK:
553 case InputDevice.SOURCE_GAMEPAD:
554 case InputDevice.SOURCE_DPAD:
555 return SDLControllerManager.handleJoystickMotionEvent(event);
557 case InputDevice.SOURCE_MOUSE:
558 action = event.getActionMasked();
560 case MotionEvent.ACTION_SCROLL:
561 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
562 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
563 SDLActivity.onNativeMouse(0, action, x, y, false);
566 case MotionEvent.ACTION_HOVER_MOVE:
570 SDLActivity.onNativeMouse(0, action, x, y, false);
582 // Event was not managed
586 public boolean supportsRelativeMouse() {
590 public boolean inRelativeMode() {
594 public boolean setRelativeMouseEnabled(boolean enabled) {
598 public void reclaimRelativeMouseModeIfNeeded()
603 public float getEventX(MotionEvent event) {
604 return event.getX(0);
607 public float getEventY(MotionEvent event) {
608 return event.getY(0);
613 class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
614 // Generic Motion (mouse hover, joystick...) events go here
616 private boolean mRelativeModeEnabled;
619 public boolean onGenericMotion(View v, MotionEvent event) {
621 // Handle relative mouse mode
622 if (mRelativeModeEnabled) {
623 if (event.getSource() == InputDevice.SOURCE_MOUSE) {
624 int action = event.getActionMasked();
625 if (action == MotionEvent.ACTION_HOVER_MOVE) {
626 float x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
627 float y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
628 SDLActivity.onNativeMouse(0, action, x, y, true);
634 // Event was not managed, call SDLGenericMotionListener_API12 method
635 return super.onGenericMotion(v, event);
639 public boolean supportsRelativeMouse() {
644 public boolean inRelativeMode() {
645 return mRelativeModeEnabled;
649 public boolean setRelativeMouseEnabled(boolean enabled) {
650 mRelativeModeEnabled = enabled;
655 public float getEventX(MotionEvent event) {
656 if (mRelativeModeEnabled) {
657 return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
660 return event.getX(0);
665 public float getEventY(MotionEvent event) {
666 if (mRelativeModeEnabled) {
667 return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
670 return event.getY(0);
676 class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
677 // Generic Motion (mouse hover, joystick...) events go here
678 private boolean mRelativeModeEnabled;
681 public boolean onGenericMotion(View v, MotionEvent event) {
685 switch ( event.getSource() ) {
686 case InputDevice.SOURCE_JOYSTICK:
687 case InputDevice.SOURCE_GAMEPAD:
688 case InputDevice.SOURCE_DPAD:
689 return SDLControllerManager.handleJoystickMotionEvent(event);
691 case InputDevice.SOURCE_MOUSE:
692 // DeX desktop mouse cursor is a separate non-standard input type.
693 case InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN:
694 action = event.getActionMasked();
696 case MotionEvent.ACTION_SCROLL:
697 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
698 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
699 SDLActivity.onNativeMouse(0, action, x, y, false);
702 case MotionEvent.ACTION_HOVER_MOVE:
705 SDLActivity.onNativeMouse(0, action, x, y, false);
713 case InputDevice.SOURCE_MOUSE_RELATIVE:
714 action = event.getActionMasked();
716 case MotionEvent.ACTION_SCROLL:
717 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
718 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
719 SDLActivity.onNativeMouse(0, action, x, y, false);
722 case MotionEvent.ACTION_HOVER_MOVE:
725 SDLActivity.onNativeMouse(0, action, x, y, true);
737 // Event was not managed
742 public boolean supportsRelativeMouse() {
743 return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
747 public boolean inRelativeMode() {
748 return mRelativeModeEnabled;
752 public boolean setRelativeMouseEnabled(boolean enabled) {
753 if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
755 SDLActivity.getContentView().requestPointerCapture();
758 SDLActivity.getContentView().releasePointerCapture();
760 mRelativeModeEnabled = enabled;
770 public void reclaimRelativeMouseModeIfNeeded()
772 if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
773 SDLActivity.getContentView().requestPointerCapture();
778 public float getEventX(MotionEvent event) {
779 // Relative mouse in capture mode will only have relative for X/Y
780 return event.getX(0);
784 public float getEventY(MotionEvent event) {
785 // Relative mouse in capture mode will only have relative for X/Y
786 return event.getY(0);