added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / build-projects / android / app / src / main / java / org / libsdl / app / SDLControllerManager.java
1 package org.libsdl.app;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.List;
7
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;
17
18
19 public class SDLControllerManager
20 {
21
22     public static native int nativeSetupJNI();
23
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,
34                                           float value);
35     public static native void onNativeHat(int device_id, int hat_id,
36                                           int x, int y);
37
38     protected static SDLJoystickHandler mJoystickHandler;
39     protected static SDLHapticHandler mHapticHandler;
40
41     private static final String TAG = "SDLControllerManager";
42
43     public static void initialize() {
44         if (mJoystickHandler == null) {
45             if (Build.VERSION.SDK_INT >= 19) {
46                 mJoystickHandler = new SDLJoystickHandler_API19();
47             } else {
48                 mJoystickHandler = new SDLJoystickHandler_API16();
49             }
50         }
51
52         if (mHapticHandler == null) {
53             if (Build.VERSION.SDK_INT >= 26) {
54                 mHapticHandler = new SDLHapticHandler_API26();
55             } else {
56                 mHapticHandler = new SDLHapticHandler();
57             }
58         }
59     }
60
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);
64     }
65
66     /**
67      * This method is called by SDL using JNI.
68      */
69     public static void pollInputDevices() {
70         mJoystickHandler.pollInputDevices();
71     }
72
73     /**
74      * This method is called by SDL using JNI.
75      */
76     public static void pollHapticDevices() {
77         mHapticHandler.pollHapticDevices();
78     }
79
80     /**
81      * This method is called by SDL using JNI.
82      */
83     public static void hapticRun(int device_id, float intensity, int length) {
84         mHapticHandler.run(device_id, intensity, length);
85     }
86
87     /**
88      * This method is called by SDL using JNI.
89      */
90     public static void hapticStop(int device_id)
91     {
92         mHapticHandler.stop(device_id);
93     }
94
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)) {
101             return false;
102         }
103         int sources = device.getSources();
104
105         /* This is called for every button press, so let's not spam the logs */
106         /*
107         if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
108             Log.v(TAG, "Input device " + device.getName() + " has class joystick.");
109         }
110         if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
111             Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
112         }
113         if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
114             Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
115         }
116         */
117
118         return ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ||
119                 ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
120                 ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
121         );
122     }
123
124 }
125
126 class SDLJoystickHandler {
127
128     /**
129      * Handles given MotionEvent.
130      * @param event the event to be handled.
131      * @return if given event was processed.
132      */
133     public boolean handleMotionEvent(MotionEvent event) {
134         return false;
135     }
136
137     /**
138      * Handles adding and removing of input devices.
139      */
140     public void pollInputDevices() {
141     }
142 }
143
144 /* Actual joystick functionality available for API >= 12 devices */
145 class SDLJoystickHandler_API16 extends SDLJoystickHandler {
146
147     static class SDLJoystick {
148         public int device_id;
149         public String name;
150         public String desc;
151         public ArrayList<InputDevice.MotionRange> axes;
152         public ArrayList<InputDevice.MotionRange> hats;
153     }
154     static class RangeComparator implements Comparator<InputDevice.MotionRange> {
155         @Override
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;
164             }
165             if (arg1Axis == MotionEvent.AXIS_GAS) {
166                 arg1Axis = MotionEvent.AXIS_BRAKE;
167             } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
168                 arg1Axis = MotionEvent.AXIS_GAS;
169             }
170
171             return arg0Axis - arg1Axis;
172         }
173     }
174
175     private final ArrayList<SDLJoystick> mJoysticks;
176
177     public SDLJoystickHandler_API16() {
178
179         mJoysticks = new ArrayList<SDLJoystick>();
180     }
181
182     @Override
183     public void pollInputDevices() {
184         int[] deviceIds = InputDevice.getDeviceIds();
185
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>();
197
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);
204                             } else {
205                                 joystick.axes.add(range);
206                             }
207                         }
208                     }
209
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);
214                 }
215             }
216         }
217
218         /* Check removed devices */
219         ArrayList<Integer> removedDevices = null;
220         for (SDLJoystick joystick : mJoysticks) {
221             int device_id = joystick.device_id;
222             int i;
223             for (i = 0; i < deviceIds.length; i++) {
224                 if (device_id == deviceIds[i]) break;
225             }
226             if (i == deviceIds.length) {
227                 if (removedDevices == null) {
228                     removedDevices = new ArrayList<Integer>();
229                 }
230                 removedDevices.add(device_id);
231             }
232         }
233
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);
240                         break;
241                     }
242                 }
243             }
244         }
245     }
246
247     protected SDLJoystick getJoystick(int device_id) {
248         for (SDLJoystick joystick : mJoysticks) {
249             if (joystick.device_id == device_id) {
250                 return joystick;
251             }
252         }
253         return null;
254     }
255
256     @Override
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);
269                     }
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);
274                     }
275                 }
276             }
277         }
278         return true;
279     }
280
281     public String getJoystickDescriptor(InputDevice joystickDevice) {
282         String desc = joystickDevice.getDescriptor();
283
284         if (desc != null && !desc.isEmpty()) {
285             return desc;
286         }
287
288         return joystickDevice.getName();
289     }
290     public int getProductId(InputDevice joystickDevice) {
291         return 0;
292     }
293     public int getVendorId(InputDevice joystickDevice) {
294         return 0;
295     }
296     public int getButtonMask(InputDevice joystickDevice) {
297         return -1;
298     }
299 }
300
301 class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
302
303     @Override
304     public int getProductId(InputDevice joystickDevice) {
305         return joystickDevice.getProductId();
306     }
307
308     @Override
309     public int getVendorId(InputDevice joystickDevice) {
310         return joystickDevice.getVendorId();
311     }
312
313     @Override
314     public int getButtonMask(InputDevice joystickDevice) {
315         int button_mask = 0;
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,
334
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,
356         };
357         int[] masks = new int[] {
358             (1 << 0),   // A -> A
359             (1 << 1),   // B -> B
360             (1 << 2),   // X -> X
361             (1 << 3),   // Y -> Y
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 -> ??
396         };
397         boolean[] has_keys = joystickDevice.hasKeys(keys);
398         for (int i = 0; i < keys.length; ++i) {
399             if (has_keys[i]) {
400                 button_mask |= masks[i];
401             }
402         }
403         return button_mask;
404     }
405 }
406
407 class SDLHapticHandler_API26 extends SDLHapticHandler {
408     @Override
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) {
414                 stop(device_id);
415                 return;
416             }
417
418             int vibeValue = Math.round(intensity * 255);
419
420             if (vibeValue > 255) {
421                 vibeValue = 255;
422             }
423             if (vibeValue < 1) {
424                 stop(device_id);
425                 return;
426             }
427             try {
428                 haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
429             }
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);
434             }
435         }
436     }
437 }
438
439 class SDLHapticHandler {
440
441     static class SDLHaptic {
442         public int device_id;
443         public String name;
444         public Vibrator vib;
445     }
446
447     private final ArrayList<SDLHaptic> mHaptics;
448
449     public SDLHapticHandler() {
450         mHaptics = new ArrayList<SDLHaptic>();
451     }
452
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);
457         }
458     }
459
460     public void stop(int device_id) {
461         SDLHaptic haptic = getHaptic(device_id);
462         if (haptic != null) {
463             haptic.vib.cancel();
464         }
465     }
466
467     public void pollHapticDevices() {
468
469         final int deviceId_VIBRATOR_SERVICE = 999999;
470         boolean hasVibratorService = false;
471
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
477
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();
487                     haptic.vib = vib;
488                     mHaptics.add(haptic);
489                     SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
490                 }
491             }
492         }
493
494         /* Check VIBRATOR_SERVICE */
495         Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
496         if (vib != null) {
497             hasVibratorService = vib.hasVibrator();
498
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";
505                     haptic.vib = vib;
506                     mHaptics.add(haptic);
507                     SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
508                 }
509             }
510         }
511
512         /* Check removed devices */
513         ArrayList<Integer> removedDevices = null;
514         for (SDLHaptic haptic : mHaptics) {
515             int device_id = haptic.device_id;
516             int i;
517             for (i = 0; i < deviceIds.length; i++) {
518                 if (device_id == deviceIds[i]) break;
519             }
520
521             if (device_id != deviceId_VIBRATOR_SERVICE || !hasVibratorService) {
522                 if (i == deviceIds.length) {
523                     if (removedDevices == null) {
524                         removedDevices = new ArrayList<Integer>();
525                     }
526                     removedDevices.add(device_id);
527                 }
528             }  // else: don't remove the vibrator if it is still present
529         }
530
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) {
536                         mHaptics.remove(i);
537                         break;
538                     }
539                 }
540             }
541         }
542     }
543
544     protected SDLHaptic getHaptic(int device_id) {
545         for (SDLHaptic haptic : mHaptics) {
546             if (haptic.device_id == device_id) {
547                 return haptic;
548             }
549         }
550         return null;
551     }
552 }
553
554 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
555     // Generic Motion (mouse hover, joystick...) events go here
556     @Override
557     public boolean onGenericMotion(View v, MotionEvent event) {
558         float x, y;
559         int action;
560
561         switch ( event.getSource() ) {
562             case InputDevice.SOURCE_JOYSTICK:
563             case InputDevice.SOURCE_GAMEPAD:
564             case InputDevice.SOURCE_DPAD:
565                 return SDLControllerManager.handleJoystickMotionEvent(event);
566
567             case InputDevice.SOURCE_MOUSE:
568                 action = event.getActionMasked();
569                 switch (action) {
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);
574                         return true;
575
576                     case MotionEvent.ACTION_HOVER_MOVE:
577                         x = event.getX(0);
578                         y = event.getY(0);
579
580                         SDLActivity.onNativeMouse(0, action, x, y, false);
581                         return true;
582
583                     default:
584                         break;
585                 }
586                 break;
587
588             default:
589                 break;
590         }
591
592         // Event was not managed
593         return false;
594     }
595
596     public boolean supportsRelativeMouse() {
597         return false;
598     }
599
600     public boolean inRelativeMode() {
601         return false;
602     }
603
604     public boolean setRelativeMouseEnabled(boolean enabled) {
605         return false;
606     }
607
608     public void reclaimRelativeMouseModeIfNeeded()
609     {
610
611     }
612
613     public float getEventX(MotionEvent event) {
614         return event.getX(0);
615     }
616
617     public float getEventY(MotionEvent event) {
618         return event.getY(0);
619     }
620
621 }
622
623 class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
624     // Generic Motion (mouse hover, joystick...) events go here
625
626     private boolean mRelativeModeEnabled;
627
628     @Override
629     public boolean onGenericMotion(View v, MotionEvent event) {
630
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);
639                     return true;
640                 }
641             }
642         }
643
644         // Event was not managed, call SDLGenericMotionListener_API12 method
645         return super.onGenericMotion(v, event);
646     }
647
648     @Override
649     public boolean supportsRelativeMouse() {
650         return true;
651     }
652
653     @Override
654     public boolean inRelativeMode() {
655         return mRelativeModeEnabled;
656     }
657
658     @Override
659     public boolean setRelativeMouseEnabled(boolean enabled) {
660         mRelativeModeEnabled = enabled;
661         return true;
662     }
663
664     @Override
665     public float getEventX(MotionEvent event) {
666         if (mRelativeModeEnabled) {
667             return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
668         } else {
669             return event.getX(0);
670         }
671     }
672
673     @Override
674     public float getEventY(MotionEvent event) {
675         if (mRelativeModeEnabled) {
676             return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
677         } else {
678             return event.getY(0);
679         }
680     }
681 }
682
683 class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
684     // Generic Motion (mouse hover, joystick...) events go here
685     private boolean mRelativeModeEnabled;
686
687     @Override
688     public boolean onGenericMotion(View v, MotionEvent event) {
689         float x, y;
690         int action;
691
692         switch ( event.getSource() ) {
693             case InputDevice.SOURCE_JOYSTICK:
694             case InputDevice.SOURCE_GAMEPAD:
695             case InputDevice.SOURCE_DPAD:
696                 return SDLControllerManager.handleJoystickMotionEvent(event);
697
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();
702                 switch (action) {
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);
707                         return true;
708
709                     case MotionEvent.ACTION_HOVER_MOVE:
710                         x = event.getX(0);
711                         y = event.getY(0);
712                         SDLActivity.onNativeMouse(0, action, x, y, false);
713                         return true;
714
715                     default:
716                         break;
717                 }
718                 break;
719
720             case InputDevice.SOURCE_MOUSE_RELATIVE:
721                 action = event.getActionMasked();
722                 switch (action) {
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);
727                         return true;
728
729                     case MotionEvent.ACTION_HOVER_MOVE:
730                         x = event.getX(0);
731                         y = event.getY(0);
732                         SDLActivity.onNativeMouse(0, action, x, y, true);
733                         return true;
734
735                     default:
736                         break;
737                 }
738                 break;
739
740             default:
741                 break;
742         }
743
744         // Event was not managed
745         return false;
746     }
747
748     @Override
749     public boolean supportsRelativeMouse() {
750         return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
751     }
752
753     @Override
754     public boolean inRelativeMode() {
755         return mRelativeModeEnabled;
756     }
757
758     @Override
759     public boolean setRelativeMouseEnabled(boolean enabled) {
760         if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
761             if (enabled) {
762                 SDLActivity.getContentView().requestPointerCapture();
763             } else {
764                 SDLActivity.getContentView().releasePointerCapture();
765             }
766             mRelativeModeEnabled = enabled;
767             return true;
768         } else {
769             return false;
770         }
771     }
772
773     @Override
774     public void reclaimRelativeMouseModeIfNeeded()
775     {
776         if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
777             SDLActivity.getContentView().requestPointerCapture();
778         }
779     }
780
781     @Override
782     public float getEventX(MotionEvent event) {
783         // Relative mouse in capture mode will only have relative for X/Y
784         return event.getX(0);
785     }
786
787     @Override
788     public float getEventY(MotionEvent event) {
789         // Relative mouse in capture mode will only have relative for X/Y
790         return event.getY(0);
791     }
792 }