Separated out SDL Android java code so audio, controller, and filesystem APIs can be used independently of the SDL activity, in Qt apps for example.
authorSam Lantinga <slouken@libsdl.org>
Fri, 22 Sep 2017 08:31:56 -0700
changeset 11535fec8d897e79f
parent 11534 30c06adff2c3
child 11536 3dda7805448a
Separated out SDL Android java code so audio, controller, and filesystem APIs can be used independently of the SDL activity, in Qt apps for example.
android-project/src/org/libsdl/app/SDL.java
android-project/src/org/libsdl/app/SDLAudioManager.java
android-project/src/org/libsdl/app/SDLControllerManager.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/android-project/src/org/libsdl/app/SDL.java	Fri Sep 22 08:31:56 2017 -0700
     1.3 @@ -0,0 +1,37 @@
     1.4 +package org.libsdl.app;
     1.5 +
     1.6 +import android.content.Context;
     1.7 +
     1.8 +/**
     1.9 +    SDL library initialization
    1.10 +*/
    1.11 +public class SDL {
    1.12 +
    1.13 +    // This function should be called first and sets up the native code
    1.14 +    // so it can call into the Java classes
    1.15 +    public static void setupJNI() {
    1.16 +        SDLActivity.nativeSetupJNI();
    1.17 +        SDLAudioManager.nativeSetupJNI();
    1.18 +        SDLControllerManager.nativeSetupJNI();
    1.19 +    }
    1.20 +
    1.21 +    // This function should be called each time the activity is started
    1.22 +    public static void initialize() {
    1.23 +        setContext(null);
    1.24 +
    1.25 +        SDLActivity.initialize();
    1.26 +        SDLAudioManager.initialize();
    1.27 +        SDLControllerManager.initialize();
    1.28 +    }
    1.29 +
    1.30 +    // This function stores the current activity (SDL or not)
    1.31 +    public static void setContext(Context context) {
    1.32 +        mContext = context;
    1.33 +    }
    1.34 +
    1.35 +    public static Context getContext() {
    1.36 +        return mContext;
    1.37 +    }
    1.38 +
    1.39 +    protected static Context mContext;
    1.40 +}
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/android-project/src/org/libsdl/app/SDLAudioManager.java	Fri Sep 22 08:31:56 2017 -0700
     2.3 @@ -0,0 +1,179 @@
     2.4 +package org.libsdl.app;
     2.5 +
     2.6 +import android.media.*;
     2.7 +import android.hardware.*;
     2.8 +import android.util.Log;
     2.9 +
    2.10 +public class SDLAudioManager
    2.11 +{
    2.12 +    protected static final String TAG = "SDLAudio";
    2.13 +
    2.14 +    protected static AudioTrack mAudioTrack;
    2.15 +    protected static AudioRecord mAudioRecord;
    2.16 +
    2.17 +    public static void initialize() {
    2.18 +        mAudioTrack = null;
    2.19 +        mAudioRecord = null;
    2.20 +    }
    2.21 +
    2.22 +    // Audio
    2.23 +
    2.24 +    /**
    2.25 +     * This method is called by SDL using JNI.
    2.26 +     */
    2.27 +    public static int audioOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
    2.28 +        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
    2.29 +        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
    2.30 +        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
    2.31 +
    2.32 +        Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
    2.33 +
    2.34 +        // Let the user pick a larger buffer if they really want -- but ye
    2.35 +        // gods they probably shouldn't, the minimums are horrifyingly high
    2.36 +        // latency already
    2.37 +        desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
    2.38 +
    2.39 +        if (mAudioTrack == null) {
    2.40 +            mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
    2.41 +                    channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
    2.42 +
    2.43 +            // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
    2.44 +            // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
    2.45 +            // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
    2.46 +
    2.47 +            if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
    2.48 +                Log.e(TAG, "Failed during initialization of Audio Track");
    2.49 +                mAudioTrack = null;
    2.50 +                return -1;
    2.51 +            }
    2.52 +
    2.53 +            mAudioTrack.play();
    2.54 +        }
    2.55 +
    2.56 +        Log.v(TAG, "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
    2.57 +
    2.58 +        return 0;
    2.59 +    }
    2.60 +
    2.61 +    /**
    2.62 +     * This method is called by SDL using JNI.
    2.63 +     */
    2.64 +    public static void audioWriteShortBuffer(short[] buffer) {
    2.65 +        if (mAudioTrack == null) {
    2.66 +            Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
    2.67 +            return;
    2.68 +        }
    2.69 +
    2.70 +        for (int i = 0; i < buffer.length; ) {
    2.71 +            int result = mAudioTrack.write(buffer, i, buffer.length - i);
    2.72 +            if (result > 0) {
    2.73 +                i += result;
    2.74 +            } else if (result == 0) {
    2.75 +                try {
    2.76 +                    Thread.sleep(1);
    2.77 +                } catch(InterruptedException e) {
    2.78 +                    // Nom nom
    2.79 +                }
    2.80 +            } else {
    2.81 +                Log.w(TAG, "SDL audio: error return from write(short)");
    2.82 +                return;
    2.83 +            }
    2.84 +        }
    2.85 +    }
    2.86 +
    2.87 +    /**
    2.88 +     * This method is called by SDL using JNI.
    2.89 +     */
    2.90 +    public static void audioWriteByteBuffer(byte[] buffer) {
    2.91 +        if (mAudioTrack == null) {
    2.92 +            Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
    2.93 +            return;
    2.94 +        }
    2.95 +        
    2.96 +        for (int i = 0; i < buffer.length; ) {
    2.97 +            int result = mAudioTrack.write(buffer, i, buffer.length - i);
    2.98 +            if (result > 0) {
    2.99 +                i += result;
   2.100 +            } else if (result == 0) {
   2.101 +                try {
   2.102 +                    Thread.sleep(1);
   2.103 +                } catch(InterruptedException e) {
   2.104 +                    // Nom nom
   2.105 +                }
   2.106 +            } else {
   2.107 +                Log.w(TAG, "SDL audio: error return from write(byte)");
   2.108 +                return;
   2.109 +            }
   2.110 +        }
   2.111 +    }
   2.112 +
   2.113 +    /**
   2.114 +     * This method is called by SDL using JNI.
   2.115 +     */
   2.116 +    public static int captureOpen(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
   2.117 +        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
   2.118 +        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
   2.119 +        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
   2.120 +
   2.121 +        Log.v(TAG, "SDL capture: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
   2.122 +
   2.123 +        // Let the user pick a larger buffer if they really want -- but ye
   2.124 +        // gods they probably shouldn't, the minimums are horrifyingly high
   2.125 +        // latency already
   2.126 +        desiredFrames = Math.max(desiredFrames, (AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
   2.127 +
   2.128 +        if (mAudioRecord == null) {
   2.129 +            mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
   2.130 +                    channelConfig, audioFormat, desiredFrames * frameSize);
   2.131 +
   2.132 +            // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
   2.133 +            if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
   2.134 +                Log.e(TAG, "Failed during initialization of AudioRecord");
   2.135 +                mAudioRecord.release();
   2.136 +                mAudioRecord = null;
   2.137 +                return -1;
   2.138 +            }
   2.139 +
   2.140 +            mAudioRecord.startRecording();
   2.141 +        }
   2.142 +
   2.143 +        Log.v(TAG, "SDL capture: got " + ((mAudioRecord.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioRecord.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioRecord.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
   2.144 +
   2.145 +        return 0;
   2.146 +    }
   2.147 +
   2.148 +    /** This method is called by SDL using JNI. */
   2.149 +    public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
   2.150 +        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
   2.151 +        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
   2.152 +        return mAudioRecord.read(buffer, 0, buffer.length);
   2.153 +    }
   2.154 +
   2.155 +    /** This method is called by SDL using JNI. */
   2.156 +    public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
   2.157 +        // !!! FIXME: this is available in API Level 23. Until then, we always block.  :(
   2.158 +        //return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
   2.159 +        return mAudioRecord.read(buffer, 0, buffer.length);
   2.160 +    }
   2.161 +
   2.162 +
   2.163 +    /** This method is called by SDL using JNI. */
   2.164 +    public static void audioClose() {
   2.165 +        if (mAudioTrack != null) {
   2.166 +            mAudioTrack.stop();
   2.167 +            mAudioTrack.release();
   2.168 +            mAudioTrack = null;
   2.169 +        }
   2.170 +    }
   2.171 +
   2.172 +    /** This method is called by SDL using JNI. */
   2.173 +    public static void captureClose() {
   2.174 +        if (mAudioRecord != null) {
   2.175 +            mAudioRecord.stop();
   2.176 +            mAudioRecord.release();
   2.177 +            mAudioRecord = null;
   2.178 +        }
   2.179 +    }
   2.180 +
   2.181 +    public static native int nativeSetupJNI();
   2.182 +}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/android-project/src/org/libsdl/app/SDLControllerManager.java	Fri Sep 22 08:31:56 2017 -0700
     3.3 @@ -0,0 +1,427 @@
     3.4 +package org.libsdl.app;
     3.5 +
     3.6 +import java.util.ArrayList;
     3.7 +import java.util.Arrays;
     3.8 +import java.util.Collections;
     3.9 +import java.util.Comparator;
    3.10 +import java.util.List;
    3.11 +
    3.12 +import android.app.*;
    3.13 +import android.content.Context;
    3.14 +import android.hardware.*;
    3.15 +import android.os.*;
    3.16 +import android.view.*;
    3.17 +import android.util.Log;
    3.18 +
    3.19 +
    3.20 +public class SDLControllerManager 
    3.21 +{
    3.22 +
    3.23 +    public static native int nativeSetupJNI();
    3.24 +
    3.25 +    public static native int nativeAddJoystick(int device_id, String name, String desc,
    3.26 +                                               int is_accelerometer, int nbuttons,
    3.27 +                                               int naxes, int nhats, int nballs);
    3.28 +    public static native int nativeRemoveJoystick(int device_id);
    3.29 +    public static native int nativeAddHaptic(int device_id, String name);
    3.30 +    public static native int nativeRemoveHaptic(int device_id);
    3.31 +    public static native int onNativePadDown(int device_id, int keycode);
    3.32 +    public static native int onNativePadUp(int device_id, int keycode);
    3.33 +    public static native void onNativeJoy(int device_id, int axis,
    3.34 +                                          float value);
    3.35 +    public static native void onNativeHat(int device_id, int hat_id,
    3.36 +                                          int x, int y);
    3.37 +
    3.38 +    protected static SDLJoystickHandler mJoystickHandler;
    3.39 +    protected static SDLHapticHandler mHapticHandler;
    3.40 +
    3.41 +    private static final String TAG = "SDLControllerManager";
    3.42 +
    3.43 +    public static void initialize() {
    3.44 +        mJoystickHandler = null;
    3.45 +        mHapticHandler = null;
    3.46 +
    3.47 +        SDLControllerManager.setup();
    3.48 +    }
    3.49 +
    3.50 +    public static void setup() {
    3.51 +        if (Build.VERSION.SDK_INT >= 16) {
    3.52 +            mJoystickHandler = new SDLJoystickHandler_API16();
    3.53 +        } else if (Build.VERSION.SDK_INT >= 12) {
    3.54 +            mJoystickHandler = new SDLJoystickHandler_API12();
    3.55 +        } else {
    3.56 +            mJoystickHandler = new SDLJoystickHandler();
    3.57 +        }
    3.58 +        mHapticHandler = new SDLHapticHandler();
    3.59 +    }
    3.60 +
    3.61 +    // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
    3.62 +    public static boolean handleJoystickMotionEvent(MotionEvent event) {
    3.63 +        return mJoystickHandler.handleMotionEvent(event);
    3.64 +    }
    3.65 +
    3.66 +    /**
    3.67 +     * This method is called by SDL using JNI.
    3.68 +     */
    3.69 +    public static void pollInputDevices() {
    3.70 +        mJoystickHandler.pollInputDevices();
    3.71 +    }
    3.72 +
    3.73 +    /**
    3.74 +     * This method is called by SDL using JNI.
    3.75 +     */
    3.76 +    public static void pollHapticDevices() {
    3.77 +        mHapticHandler.pollHapticDevices();
    3.78 +    }
    3.79 +
    3.80 +    /**
    3.81 +     * This method is called by SDL using JNI.
    3.82 +     */
    3.83 +    public static void hapticRun(int device_id, int length) {
    3.84 +        mHapticHandler.run(device_id, length);
    3.85 +    }
    3.86 +
    3.87 +    // Check if a given device is considered a possible SDL joystick
    3.88 +    public static boolean isDeviceSDLJoystick(int deviceId) {
    3.89 +        InputDevice device = InputDevice.getDevice(deviceId);
    3.90 +        // We cannot use InputDevice.isVirtual before API 16, so let's accept
    3.91 +        // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
    3.92 +        if ((device == null) || (deviceId < 0)) {
    3.93 +            return false;
    3.94 +        }
    3.95 +        int sources = device.getSources();
    3.96 +
    3.97 +        if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) {
    3.98 +            Log.v(TAG, "Input device " + device.getName() + " is a joystick.");
    3.99 +        }
   3.100 +        if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
   3.101 +            Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
   3.102 +        }
   3.103 +        if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
   3.104 +            Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
   3.105 +        }
   3.106 +
   3.107 +        return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
   3.108 +                ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
   3.109 +                ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
   3.110 +        );
   3.111 +    }
   3.112 +
   3.113 +}
   3.114 +
   3.115 +/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
   3.116 +class SDLJoystickHandler {
   3.117 +
   3.118 +    /**
   3.119 +     * Handles given MotionEvent.
   3.120 +     * @param event the event to be handled.
   3.121 +     * @return if given event was processed.
   3.122 +     */
   3.123 +    public boolean handleMotionEvent(MotionEvent event) {
   3.124 +        return false;
   3.125 +    }
   3.126 +
   3.127 +    /**
   3.128 +     * Handles adding and removing of input devices.
   3.129 +     */
   3.130 +    public void pollInputDevices() {
   3.131 +    }
   3.132 +}
   3.133 +
   3.134 +/* Actual joystick functionality available for API >= 12 devices */
   3.135 +class SDLJoystickHandler_API12 extends SDLJoystickHandler {
   3.136 +
   3.137 +    static class SDLJoystick {
   3.138 +        public int device_id;
   3.139 +        public String name;
   3.140 +        public String desc;
   3.141 +        public ArrayList<InputDevice.MotionRange> axes;
   3.142 +        public ArrayList<InputDevice.MotionRange> hats;
   3.143 +    }
   3.144 +    static class RangeComparator implements Comparator<InputDevice.MotionRange> {
   3.145 +        @Override
   3.146 +        public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
   3.147 +            return arg0.getAxis() - arg1.getAxis();
   3.148 +        }
   3.149 +    }
   3.150 +
   3.151 +    private ArrayList<SDLJoystick> mJoysticks;
   3.152 +
   3.153 +    public SDLJoystickHandler_API12() {
   3.154 +
   3.155 +        mJoysticks = new ArrayList<SDLJoystick>();
   3.156 +    }
   3.157 +
   3.158 +    @Override
   3.159 +    public void pollInputDevices() {
   3.160 +        int[] deviceIds = InputDevice.getDeviceIds();
   3.161 +        // It helps processing the device ids in reverse order
   3.162 +        // For example, in the case of the XBox 360 wireless dongle,
   3.163 +        // so the first controller seen by SDL matches what the receiver
   3.164 +        // considers to be the first controller
   3.165 +
   3.166 +        for(int i=deviceIds.length-1; i>-1; i--) {
   3.167 +            SDLJoystick joystick = getJoystick(deviceIds[i]);
   3.168 +            if (joystick == null) {
   3.169 +                joystick = new SDLJoystick();
   3.170 +                InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
   3.171 +                if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
   3.172 +                    joystick.device_id = deviceIds[i];
   3.173 +                    joystick.name = joystickDevice.getName();
   3.174 +                    joystick.desc = getJoystickDescriptor(joystickDevice);
   3.175 +                    joystick.axes = new ArrayList<InputDevice.MotionRange>();
   3.176 +                    joystick.hats = new ArrayList<InputDevice.MotionRange>();
   3.177 +
   3.178 +                    List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
   3.179 +                    Collections.sort(ranges, new RangeComparator());
   3.180 +                    for (InputDevice.MotionRange range : ranges ) {
   3.181 +                        if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
   3.182 +                            if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
   3.183 +                                range.getAxis() == MotionEvent.AXIS_HAT_Y) {
   3.184 +                                joystick.hats.add(range);
   3.185 +                            }
   3.186 +                            else {
   3.187 +                                joystick.axes.add(range);
   3.188 +                            }
   3.189 +                        }
   3.190 +                    }
   3.191 +
   3.192 +                    mJoysticks.add(joystick);
   3.193 +                    SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, 0, -1,
   3.194 +                                                           joystick.axes.size(), joystick.hats.size()/2, 0);
   3.195 +                }
   3.196 +            }
   3.197 +        }
   3.198 +
   3.199 +        /* Check removed devices */
   3.200 +        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
   3.201 +        for(int i=0; i < mJoysticks.size(); i++) {
   3.202 +            int device_id = mJoysticks.get(i).device_id;
   3.203 +            int j;
   3.204 +            for (j=0; j < deviceIds.length; j++) {
   3.205 +                if (device_id == deviceIds[j]) break;
   3.206 +            }
   3.207 +            if (j == deviceIds.length) {
   3.208 +                removedDevices.add(Integer.valueOf(device_id));
   3.209 +            }
   3.210 +        }
   3.211 +
   3.212 +        for(int i=0; i < removedDevices.size(); i++) {
   3.213 +            int device_id = removedDevices.get(i).intValue();
   3.214 +            SDLControllerManager.nativeRemoveJoystick(device_id);
   3.215 +            for (int j=0; j < mJoysticks.size(); j++) {
   3.216 +                if (mJoysticks.get(j).device_id == device_id) {
   3.217 +                    mJoysticks.remove(j);
   3.218 +                    break;
   3.219 +                }
   3.220 +            }
   3.221 +        }
   3.222 +    }
   3.223 +
   3.224 +    protected SDLJoystick getJoystick(int device_id) {
   3.225 +        for(int i=0; i < mJoysticks.size(); i++) {
   3.226 +            if (mJoysticks.get(i).device_id == device_id) {
   3.227 +                return mJoysticks.get(i);
   3.228 +            }
   3.229 +        }
   3.230 +        return null;
   3.231 +    }
   3.232 +
   3.233 +    @Override
   3.234 +    public boolean handleMotionEvent(MotionEvent event) {
   3.235 +        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
   3.236 +            int actionPointerIndex = event.getActionIndex();
   3.237 +            int action = event.getActionMasked();
   3.238 +            switch(action) {
   3.239 +                case MotionEvent.ACTION_MOVE:
   3.240 +                    SDLJoystick joystick = getJoystick(event.getDeviceId());
   3.241 +                    if ( joystick != null ) {
   3.242 +                        for (int i = 0; i < joystick.axes.size(); i++) {
   3.243 +                            InputDevice.MotionRange range = joystick.axes.get(i);
   3.244 +                            /* Normalize the value to -1...1 */
   3.245 +                            float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
   3.246 +                            SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
   3.247 +                        }
   3.248 +                        for (int i = 0; i < joystick.hats.size(); i+=2) {
   3.249 +                            int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
   3.250 +                            int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
   3.251 +                            SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
   3.252 +                        }
   3.253 +                    }
   3.254 +                    break;
   3.255 +                default:
   3.256 +                    break;
   3.257 +            }
   3.258 +        }
   3.259 +        return true;
   3.260 +    }
   3.261 +
   3.262 +    public String getJoystickDescriptor(InputDevice joystickDevice) {
   3.263 +        return joystickDevice.getName();
   3.264 +    }
   3.265 +}
   3.266 +
   3.267 +
   3.268 +class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 {
   3.269 +
   3.270 +    @Override
   3.271 +    public String getJoystickDescriptor(InputDevice joystickDevice) {
   3.272 +        String desc = joystickDevice.getDescriptor();
   3.273 +
   3.274 +        if (desc != null && desc != "") {
   3.275 +            return desc;
   3.276 +        }
   3.277 +
   3.278 +        return super.getJoystickDescriptor(joystickDevice);
   3.279 +    }
   3.280 +}
   3.281 +
   3.282 +class SDLHapticHandler {
   3.283 +
   3.284 +    class SDLHaptic {
   3.285 +        public int device_id;
   3.286 +        public String name;
   3.287 +        public Vibrator vib;
   3.288 +    }
   3.289 +
   3.290 +    private ArrayList<SDLHaptic> mHaptics;
   3.291 +    
   3.292 +    public SDLHapticHandler() {
   3.293 +        mHaptics = new ArrayList<SDLHaptic>();
   3.294 +    }
   3.295 +
   3.296 +    public void run(int device_id, int length) {
   3.297 +        SDLHaptic haptic = getHaptic(device_id);
   3.298 +        if (haptic != null) {
   3.299 +            haptic.vib.vibrate (length);
   3.300 +        }
   3.301 +    }
   3.302 +
   3.303 +    public void pollHapticDevices() {
   3.304 +        
   3.305 +        final int deviceId_VIBRATOR_SERVICE = 999999;
   3.306 +        boolean hasVibrator = false;
   3.307 +
   3.308 +        int[] deviceIds = InputDevice.getDeviceIds();
   3.309 +        // It helps processing the device ids in reverse order
   3.310 +        // For example, in the case of the XBox 360 wireless dongle,
   3.311 +        // so the first controller seen by SDL matches what the receiver
   3.312 +        // considers to be the first controller
   3.313 +
   3.314 +        for(int i=deviceIds.length-1; i>-1; i--) {
   3.315 +            SDLHaptic haptic = getHaptic(deviceIds[i]);
   3.316 +            if (haptic == null) {
   3.317 +                InputDevice device = InputDevice.getDevice(deviceIds[i]);
   3.318 +                Vibrator vib = device.getVibrator();
   3.319 +                if (vib.hasVibrator()) {
   3.320 +                    haptic = new SDLHaptic();
   3.321 +                    haptic.device_id = deviceIds[i];
   3.322 +                    haptic.name = device.getName();
   3.323 +                    haptic.vib = vib;
   3.324 +                    mHaptics.add(haptic);
   3.325 +                    SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
   3.326 +                }
   3.327 +            }
   3.328 +        }
   3.329 +
   3.330 +        /* Check VIBRATOR_SERVICE */
   3.331 +        {
   3.332 +           Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
   3.333 +           if (vib != null && vib.hasVibrator()) {
   3.334 +              hasVibrator = true;
   3.335 +              SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
   3.336 +              if (haptic == null) {
   3.337 +                 haptic = new SDLHaptic();
   3.338 +                 haptic.device_id = deviceId_VIBRATOR_SERVICE;
   3.339 +                 haptic.name = "VIBRATOR_SERVICE";
   3.340 +                 haptic.vib = vib; 
   3.341 +                 mHaptics.add(haptic);
   3.342 +                 SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
   3.343 +              }
   3.344 +           }
   3.345 +        }
   3.346 +
   3.347 +        /* Check removed devices */
   3.348 +        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
   3.349 +        for(int i=0; i < mHaptics.size(); i++) {
   3.350 +            int device_id = mHaptics.get(i).device_id;
   3.351 +            int j;
   3.352 +            for (j=0; j < deviceIds.length; j++) {
   3.353 +                if (device_id == deviceIds[j]) break;
   3.354 +            }
   3.355 +
   3.356 +            if (device_id == deviceId_VIBRATOR_SERVICE && hasVibrator) {
   3.357 +               // don't remove the vibrator if it is still present
   3.358 +            } else if (j == deviceIds.length) {
   3.359 +                removedDevices.add(device_id);
   3.360 +            }
   3.361 +        }
   3.362 +
   3.363 +        for(int i=0; i < removedDevices.size(); i++) {
   3.364 +            int device_id = removedDevices.get(i);
   3.365 +            SDLControllerManager.nativeRemoveHaptic(device_id);
   3.366 +            for (int j=0; j < mHaptics.size(); j++) {
   3.367 +                if (mHaptics.get(j).device_id == device_id) {
   3.368 +                    mHaptics.remove(j);
   3.369 +                    break;
   3.370 +                }
   3.371 +            }
   3.372 +        }
   3.373 +    }
   3.374 +
   3.375 +    protected SDLHaptic getHaptic(int device_id) {
   3.376 +        for(int i=0; i < mHaptics.size(); i++) {
   3.377 +            if (mHaptics.get(i).device_id == device_id) {
   3.378 +                return mHaptics.get(i);
   3.379 +            }
   3.380 +        }
   3.381 +        return null;
   3.382 +    }   
   3.383 +}
   3.384 +
   3.385 +class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
   3.386 +    // Generic Motion (mouse hover, joystick...) events go here
   3.387 +    @Override
   3.388 +    public boolean onGenericMotion(View v, MotionEvent event) {
   3.389 +        float x, y;
   3.390 +        int action;
   3.391 +
   3.392 +        switch ( event.getSource() ) {
   3.393 +            case InputDevice.SOURCE_JOYSTICK:
   3.394 +            case InputDevice.SOURCE_GAMEPAD:
   3.395 +            case InputDevice.SOURCE_DPAD:
   3.396 +                return SDLControllerManager.handleJoystickMotionEvent(event);
   3.397 +
   3.398 +            case InputDevice.SOURCE_MOUSE:
   3.399 +                if (!SDLActivity.mSeparateMouseAndTouch) {
   3.400 +                    break;
   3.401 +                }
   3.402 +                action = event.getActionMasked();
   3.403 +                switch (action) {
   3.404 +                    case MotionEvent.ACTION_SCROLL:
   3.405 +                        x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
   3.406 +                        y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
   3.407 +                        SDLActivity.onNativeMouse(0, action, x, y);
   3.408 +                        return true;
   3.409 +
   3.410 +                    case MotionEvent.ACTION_HOVER_MOVE:
   3.411 +                        x = event.getX(0);
   3.412 +                        y = event.getY(0);
   3.413 +
   3.414 +                        SDLActivity.onNativeMouse(0, action, x, y);
   3.415 +                        return true;
   3.416 +
   3.417 +                    default:
   3.418 +                        break;
   3.419 +                }
   3.420 +                break;
   3.421 +
   3.422 +            default:
   3.423 +                break;
   3.424 +        }
   3.425 +
   3.426 +        // Event was not managed
   3.427 +        return false;
   3.428 +    }
   3.429 +}
   3.430 +