package org.libsdl.app; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import android.content.Context; import android.os.*; import android.view.*; import android.util.Log; public class SDLControllerManager { public static native int nativeSetupJNI(); public static native int nativeAddJoystick(int device_id, String name, String desc, int vendor_id, int product_id, boolean is_accelerometer, int button_mask, int naxes, int nhats, int nballs); 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); public static native int onNativePadDown(int device_id, int keycode); public static native int onNativePadUp(int device_id, int keycode); public static native void onNativeJoy(int device_id, int axis, float value); public static native void onNativeHat(int device_id, int hat_id, int x, int y); protected static SDLJoystickHandler mJoystickHandler; protected static SDLHapticHandler mHapticHandler; private static final String TAG = "SDLControllerManager"; public static void initialize() { mJoystickHandler = null; mHapticHandler = null; SDLControllerManager.setup(); } public static void setup() { if (Build.VERSION.SDK_INT >= 19) { mJoystickHandler = new SDLJoystickHandler_API19(); } else if (Build.VERSION.SDK_INT >= 16) { mJoystickHandler = new SDLJoystickHandler_API16(); } else if (Build.VERSION.SDK_INT >= 12) { mJoystickHandler = new SDLJoystickHandler_API12(); } else { mJoystickHandler = new SDLJoystickHandler(); } mHapticHandler = new SDLHapticHandler(); } // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance public static boolean handleJoystickMotionEvent(MotionEvent event) { return mJoystickHandler.handleMotionEvent(event); } /** * This method is called by SDL using JNI. */ public static void pollInputDevices() { mJoystickHandler.pollInputDevices(); } /** * This method is called by SDL using JNI. */ public static void pollHapticDevices() { mHapticHandler.pollHapticDevices(); } /** * This method is called by SDL using JNI. */ public static void hapticRun(int device_id, int length) { mHapticHandler.run(device_id, length); } /** * This method is called by SDL using JNI. */ public static void hapticStop(int device_id) { mHapticHandler.stop(device_id); } // Check if a given device is considered a possible SDL joystick public static boolean isDeviceSDLJoystick(int deviceId) { InputDevice device = InputDevice.getDevice(deviceId); // We cannot use InputDevice.isVirtual before API 16, so let's accept // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1) if ((device == null) || (deviceId < 0)) { return false; } int sources = device.getSources(); /* This is called for every button press, so let's not spam the logs */ /** if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) { Log.v(TAG, "Input device " + device.getName() + " is a joystick."); } if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) { Log.v(TAG, "Input device " + device.getName() + " is a dpad."); } if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { Log.v(TAG, "Input device " + device.getName() + " is a gamepad."); } **/ return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) || ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ); } } /* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ class SDLJoystickHandler { /** * Handles given MotionEvent. * @param event the event to be handled. * @return if given event was processed. */ public boolean handleMotionEvent(MotionEvent event) { return false; } /** * Handles adding and removing of input devices. */ public void pollInputDevices() { } } /* Actual joystick functionality available for API >= 12 devices */ class SDLJoystickHandler_API12 extends SDLJoystickHandler { static class SDLJoystick { public int device_id; public String name; public String desc; public ArrayList axes; public ArrayList hats; } static class RangeComparator implements Comparator { @Override public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { // 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 int arg0Axis = arg0.getAxis(); int arg1Axis = arg1.getAxis(); if (arg0Axis == MotionEvent.AXIS_GAS) { arg0Axis = MotionEvent.AXIS_BRAKE; } else if (arg0Axis == MotionEvent.AXIS_BRAKE) { arg0Axis = MotionEvent.AXIS_GAS; } if (arg1Axis == MotionEvent.AXIS_GAS) { arg1Axis = MotionEvent.AXIS_BRAKE; } else if (arg1Axis == MotionEvent.AXIS_BRAKE) { arg1Axis = MotionEvent.AXIS_GAS; } return arg0Axis - arg1Axis; } } private ArrayList mJoysticks; public SDLJoystickHandler_API12() { mJoysticks = new ArrayList(); } @Override public void pollInputDevices() { int[] deviceIds = InputDevice.getDeviceIds(); for(int i=0; i < deviceIds.length; ++i) { SDLJoystick joystick = getJoystick(deviceIds[i]); if (joystick == null) { joystick = new SDLJoystick(); InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]); if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) { joystick.device_id = deviceIds[i]; joystick.name = joystickDevice.getName(); joystick.desc = getJoystickDescriptor(joystickDevice); joystick.axes = new ArrayList(); joystick.hats = new ArrayList(); List ranges = joystickDevice.getMotionRanges(); Collections.sort(ranges, new RangeComparator()); for (InputDevice.MotionRange range : ranges ) { if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) { joystick.hats.add(range); } else { joystick.axes.add(range); } } } mJoysticks.add(joystick); SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), false, getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0); } } } /* Check removed devices */ ArrayList removedDevices = new ArrayList(); for(int i=0; i < mJoysticks.size(); i++) { int device_id = mJoysticks.get(i).device_id; int j; for (j=0; j < deviceIds.length; j++) { if (device_id == deviceIds[j]) break; } if (j == deviceIds.length) { removedDevices.add(Integer.valueOf(device_id)); } } for(int i=0; i < removedDevices.size(); i++) { int device_id = removedDevices.get(i).intValue(); SDLControllerManager.nativeRemoveJoystick(device_id); for (int j=0; j < mJoysticks.size(); j++) { if (mJoysticks.get(j).device_id == device_id) { mJoysticks.remove(j); break; } } } } protected SDLJoystick getJoystick(int device_id) { for(int i=0; i < mJoysticks.size(); i++) { if (mJoysticks.get(i).device_id == device_id) { return mJoysticks.get(i); } } return null; } @Override public boolean handleMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { int actionPointerIndex = event.getActionIndex(); int action = event.getActionMasked(); switch(action) { case MotionEvent.ACTION_MOVE: SDLJoystick joystick = getJoystick(event.getDeviceId()); if ( joystick != null ) { for (int i = 0; i < joystick.axes.size(); i++) { InputDevice.MotionRange range = joystick.axes.get(i); /* Normalize the value to -1...1 */ float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; SDLControllerManager.onNativeJoy(joystick.device_id, i, value ); } for (int i = 0; i < joystick.hats.size(); i+=2) { int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) ); int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) ); SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY ); } } break; default: break; } } return true; } public String getJoystickDescriptor(InputDevice joystickDevice) { return joystickDevice.getName(); } public int getProductId(InputDevice joystickDevice) { return 0; } public int getVendorId(InputDevice joystickDevice) { return 0; } public int getButtonMask(InputDevice joystickDevice) { return -1; } } class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 { @Override public String getJoystickDescriptor(InputDevice joystickDevice) { String desc = joystickDevice.getDescriptor(); if (desc != null && !desc.isEmpty()) { return desc; } return super.getJoystickDescriptor(joystickDevice); } } class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 { @Override public int getProductId(InputDevice joystickDevice) { return joystickDevice.getProductId(); } @Override public int getVendorId(InputDevice joystickDevice) { return joystickDevice.getVendorId(); } @Override public int getButtonMask(InputDevice joystickDevice) { int button_mask = 0; int[] keys = new int[] { KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B, KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y, KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BUTTON_MODE, KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_DPAD_CENTER, // These don't map into any SDL controller buttons directly KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2, KeyEvent.KEYCODE_BUTTON_C, KeyEvent.KEYCODE_BUTTON_Z, KeyEvent.KEYCODE_BUTTON_1, KeyEvent.KEYCODE_BUTTON_2, KeyEvent.KEYCODE_BUTTON_3, KeyEvent.KEYCODE_BUTTON_4, KeyEvent.KEYCODE_BUTTON_5, KeyEvent.KEYCODE_BUTTON_6, KeyEvent.KEYCODE_BUTTON_7, KeyEvent.KEYCODE_BUTTON_8, KeyEvent.KEYCODE_BUTTON_9, KeyEvent.KEYCODE_BUTTON_10, KeyEvent.KEYCODE_BUTTON_11, KeyEvent.KEYCODE_BUTTON_12, KeyEvent.KEYCODE_BUTTON_13, KeyEvent.KEYCODE_BUTTON_14, KeyEvent.KEYCODE_BUTTON_15, KeyEvent.KEYCODE_BUTTON_16, }; int[] masks = new int[] { (1 << 0), // A -> A (1 << 1), // B -> B (1 << 2), // X -> X (1 << 3), // Y -> Y (1 << 4), // BACK -> BACK (1 << 5), // MODE -> GUIDE (1 << 6), // START -> START (1 << 7), // THUMBL -> LEFTSTICK (1 << 8), // THUMBR -> RIGHTSTICK (1 << 9), // L1 -> LEFTSHOULDER (1 << 10), // R1 -> RIGHTSHOULDER (1 << 11), // DPAD_UP -> DPAD_UP (1 << 12), // DPAD_DOWN -> DPAD_DOWN (1 << 13), // DPAD_LEFT -> DPAD_LEFT (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT (1 << 4), // SELECT -> BACK (1 << 0), // DPAD_CENTER -> A (1 << 15), // L2 -> ?? (1 << 16), // R2 -> ?? (1 << 17), // C -> ?? (1 << 18), // Z -> ?? (1 << 20), // 1 -> ?? (1 << 21), // 2 -> ?? (1 << 22), // 3 -> ?? (1 << 23), // 4 -> ?? (1 << 24), // 5 -> ?? (1 << 25), // 6 -> ?? (1 << 26), // 7 -> ?? (1 << 27), // 8 -> ?? (1 << 28), // 9 -> ?? (1 << 29), // 10 -> ?? (1 << 30), // 11 -> ?? (1 << 31), // 12 -> ?? // We're out of room... 0xFFFFFFFF, // 13 -> ?? 0xFFFFFFFF, // 14 -> ?? 0xFFFFFFFF, // 15 -> ?? 0xFFFFFFFF, // 16 -> ?? }; boolean[] has_keys = joystickDevice.hasKeys(keys); for (int i = 0; i < keys.length; ++i) { if (has_keys[i]) { button_mask |= masks[i]; } } return button_mask; } } class SDLHapticHandler { class SDLHaptic { public int device_id; public String name; public Vibrator vib; } private ArrayList mHaptics; public SDLHapticHandler() { mHaptics = new ArrayList(); } public void run(int device_id, int length) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { haptic.vib.vibrate (length); } } public void stop(int device_id) { SDLHaptic haptic = getHaptic(device_id); if (haptic != null) { haptic.vib.cancel(); } } public void pollHapticDevices() { 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 if (Build.VERSION.SDK_INT >= 16) { 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.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) { if (Build.VERSION.SDK_INT >= 11) { hasVibratorService = vib.hasVibrator(); } else { hasVibratorService = true; } if (hasVibratorService) { SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE); if (haptic == null) { haptic = new SDLHaptic(); haptic.device_id = deviceId_VIBRATOR_SERVICE; haptic.name = "VIBRATOR_SERVICE"; haptic.vib = vib; mHaptics.add(haptic); SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name); } } } /* Check removed devices */ ArrayList removedDevices = new ArrayList(); for(int i=0; i < mHaptics.size(); i++) { int device_id = mHaptics.get(i).device_id; int j; for (j=0; j < deviceIds.length; j++) { if (device_id == deviceIds[j]) break; } if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) { // don't remove the vibrator if it is still present } else if (j == deviceIds.length) { removedDevices.add(device_id); } } for(int i=0; i < removedDevices.size(); i++) { int device_id = removedDevices.get(i); SDLControllerManager.nativeRemoveHaptic(device_id); for (int j=0; j < mHaptics.size(); j++) { if (mHaptics.get(j).device_id == device_id) { mHaptics.remove(j); break; } } } } protected SDLHaptic getHaptic(int device_id) { for(int i=0; i < mHaptics.size(); i++) { if (mHaptics.get(i).device_id == device_id) { return mHaptics.get(i); } } return null; } } class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { // Generic Motion (mouse hover, joystick...) events go here @Override public boolean onGenericMotion(View v, MotionEvent event) { float x, y; int action; switch ( event.getSource() ) { case InputDevice.SOURCE_JOYSTICK: case InputDevice.SOURCE_GAMEPAD: case InputDevice.SOURCE_DPAD: return SDLControllerManager.handleJoystickMotionEvent(event); case InputDevice.SOURCE_MOUSE: if (!SDLActivity.mSeparateMouseAndTouch) { break; } action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_SCROLL: x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; case MotionEvent.ACTION_HOVER_MOVE: x = event.getX(0); y = event.getY(0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; default: break; } break; default: break; } // Event was not managed return false; } public boolean supportsRelativeMouse() { return false; } public boolean inRelativeMode() { return false; } public boolean setRelativeMouseEnabled(boolean enabled) { return false; } public void reclaimRelativeMouseModeIfNeeded() { } public float getEventX(MotionEvent event) { return event.getX(0); } public float getEventY(MotionEvent event) { return event.getY(0); } } class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 { // Generic Motion (mouse hover, joystick...) events go here private boolean mRelativeModeEnabled; @Override public boolean onGenericMotion(View v, MotionEvent event) { float x, y; int action; switch ( event.getSource() ) { case InputDevice.SOURCE_JOYSTICK: case InputDevice.SOURCE_GAMEPAD: case InputDevice.SOURCE_DPAD: return SDLControllerManager.handleJoystickMotionEvent(event); case InputDevice.SOURCE_MOUSE: if (!SDLActivity.mSeparateMouseAndTouch) { break; } action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_SCROLL: x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; case MotionEvent.ACTION_HOVER_MOVE: if (mRelativeModeEnabled) { x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); } else { x = event.getX(0); y = event.getY(0); } SDLActivity.onNativeMouse(0, action, x, y, mRelativeModeEnabled); return true; default: break; } break; default: break; } // Event was not managed return false; } @Override public boolean supportsRelativeMouse() { return true; } @Override public boolean inRelativeMode() { return mRelativeModeEnabled; } @Override public boolean setRelativeMouseEnabled(boolean enabled) { mRelativeModeEnabled = enabled; return true; } @Override public float getEventX(MotionEvent event) { if (mRelativeModeEnabled) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X); } else { return event.getX(0); } } @Override public float getEventY(MotionEvent event) { if (mRelativeModeEnabled) { return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y); } else { return event.getY(0); } } } class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 { // Generic Motion (mouse hover, joystick...) events go here private boolean mRelativeModeEnabled; @Override public boolean onGenericMotion(View v, MotionEvent event) { float x, y; int action; switch ( event.getSource() ) { case InputDevice.SOURCE_JOYSTICK: case InputDevice.SOURCE_GAMEPAD: case InputDevice.SOURCE_DPAD: return SDLControllerManager.handleJoystickMotionEvent(event); case InputDevice.SOURCE_MOUSE: case 12290: // DeX desktop mouse cursor is a separate non-standard input type. if (!SDLActivity.mSeparateMouseAndTouch) { break; } action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_SCROLL: x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; case MotionEvent.ACTION_HOVER_MOVE: x = event.getX(0); y = event.getY(0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; default: break; } break; case InputDevice.SOURCE_MOUSE_RELATIVE: if (!SDLActivity.mSeparateMouseAndTouch) { break; } action = event.getActionMasked(); switch (action) { case MotionEvent.ACTION_SCROLL: x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); SDLActivity.onNativeMouse(0, action, x, y, false); return true; case MotionEvent.ACTION_HOVER_MOVE: x = event.getX(0); y = event.getY(0); SDLActivity.onNativeMouse(0, action, x, y, true); return true; default: break; } break; default: break; } // Event was not managed return false; } @Override public boolean supportsRelativeMouse() { return !SDLActivity.isDeXMode(); } @Override public boolean inRelativeMode() { return mRelativeModeEnabled; } @Override public boolean setRelativeMouseEnabled(boolean enabled) { if (!SDLActivity.isDeXMode()) { if (enabled) { SDLActivity.getContentView().requestPointerCapture(); } else { SDLActivity.getContentView().releasePointerCapture(); } mRelativeModeEnabled = enabled; return true; } else { return false; } } @Override public void reclaimRelativeMouseModeIfNeeded() { if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) { SDLActivity.getContentView().requestPointerCapture(); } } @Override public float getEventX(MotionEvent event) { // Relative mouse in capture mode will only have relative for X/Y return event.getX(0); } @Override public float getEventY(MotionEvent event) { // Relative mouse in capture mode will only have relative for X/Y return event.getY(0); } }