package org.libsdl.app; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; 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 is_accelerometer, int nbuttons, 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 >= 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); } // 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(); 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) { return arg0.getAxis() - arg1.getAxis(); } } private ArrayList mJoysticks; public SDLJoystickHandler_API12() { mJoysticks = new ArrayList(); } @Override public void pollInputDevices() { 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--) { 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, 0, -1, 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(); } } class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 { @Override public String getJoystickDescriptor(InputDevice joystickDevice) { String desc = joystickDevice.getDescriptor(); if (desc != null && !Objects.equals(desc, "")) { return desc; } return super.getJoystickDescriptor(joystickDevice); } } 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 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); return true; case MotionEvent.ACTION_HOVER_MOVE: x = event.getX(0); y = event.getY(0); SDLActivity.onNativeMouse(0, action, x, y); return true; default: break; } break; default: break; } // Event was not managed return false; } }