[Android] Hotplugging support for joysticks
authorGabriel Jacobo <gabomdq@gmail.com>
Tue, 10 Dec 2013 16:24:11 -0300
changeset 8057 801d84e26f91
parent 8056 abd9434c5d3e
child 8058 8e4512b74223
[Android] Hotplugging support for joysticks
WhatsNew.txt
android-project/src/org/libsdl/app/SDLActivity.java
src/core/android/SDL_android.c
src/core/android/SDL_android.h
src/joystick/android/SDL_sysjoystick.c
src/joystick/android/SDL_sysjoystick.h
src/joystick/android/SDL_sysjoystick_c.h
--- a/WhatsNew.txt	Mon Dec 09 15:17:20 2013 -0500
+++ b/WhatsNew.txt	Tue Dec 10 16:24:11 2013 -0300
@@ -16,6 +16,7 @@
 * Joystick support (minimum SDK version required to build SDL is now 12, 
   the required runtime version remains at 10, but on such devices joystick 
   support won't be available).
+* Hotplugging support for joysticks
 
 Linux:
 * Fixed fullscreen and focused behavior when receiving NotifyGrab events
--- a/android-project/src/org/libsdl/app/SDLActivity.java	Mon Dec 09 15:17:20 2013 -0500
+++ b/android-project/src/org/libsdl/app/SDLActivity.java	Tue Dec 10 16:24:11 2013 -0300
@@ -256,9 +256,9 @@
     public static native void nativePause();
     public static native void nativeResume();
     public static native void onNativeResize(int x, int y, int format);
-    public static native int onNativePadDown(int padId, int keycode);
-    public static native int onNativePadUp(int padId, int keycode);
-    public static native void onNativeJoy(int joyId, int axis,
+    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 onNativeKeyDown(int keycode);
     public static native void onNativeKeyUp(int keycode);
@@ -270,6 +270,10 @@
     public static native void onNativeSurfaceChanged();
     public static native void onNativeSurfaceDestroyed();
     public static native void nativeFlipBuffers();
+    public static native int nativeAddJoystick(int device_id, String name, 
+                                               int is_accelerometer, int nbuttons, 
+                                               int naxes, int nhats, int nballs);
+    public static native int nativeRemoveJoystick(int device_id);
 
     public static void flipBuffers() {
         SDLActivity.nativeFlipBuffers();
@@ -460,29 +464,16 @@
     }
             
     // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
-    public static int getNumJoysticks() {
-        return mJoystickHandler.getNumJoysticks();
-    }
-    
-    public static String getJoystickName(int joy) {
-        return mJoystickHandler.getJoystickName(joy);
-    }
-    
-    public static int getJoystickAxes(int joy) {
-        return mJoystickHandler.getJoystickAxes(joy);
-    }
-    
     public static boolean handleJoystickMotionEvent(MotionEvent event) {
         return mJoystickHandler.handleMotionEvent(event);
     }
     
-    /**
-     * @param devId the device id to get opened joystick id for.
-     * @return joystick id for device id or -1 if there is none.
-     */
-    public static int getJoyId(int devId) {
-        return mJoystickHandler.getJoyId(devId);
+    public static void pollInputDevices() {
+        if (SDLActivity.mSDLThread != null) {
+            mJoystickHandler.pollInputDevices();
+        }
     }
+    
 }
 
 /**
@@ -660,16 +651,13 @@
         
         if ( (event.getSource() & 0x00000401) != 0 || /* API 12: SOURCE_GAMEPAD */
                    (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
-            int id = SDLActivity.getJoyId( event.getDeviceId() );
-            if (id != -1) {
-                if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                    if (SDLActivity.onNativePadDown(id, keyCode) == 0) {
-                        return true;
-                    }
-                } else if (event.getAction() == KeyEvent.ACTION_UP) {
-                    if (SDLActivity.onNativePadUp(id, keyCode) == 0) {
-                        return true;
-                    }
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
+                    return true;
+                }
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
+                    return true;
                 }
             }
         }
@@ -916,36 +904,20 @@
 
 /* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
 class SDLJoystickHandler {
-    public int getNumJoysticks() {
-        return 0;
-    }
-    
-    public String getJoystickName(int joy) {
-        return "";
-    }
-    
-    public int getJoystickAxes(int joy) {
-        return 0;
-    }
-    
-    /**
-     * @param devId the device id to get opened joystick id for.
-     * @return joystick id for device id or -1 if there is none.
-     */
-    public int getJoyId(int devId) {
-        return -1;
-    }
     
     public boolean handleMotionEvent(MotionEvent event) {
         return false;
     }
+    
+    public void pollInputDevices() {
+    }
 }
 
 /* Actual joystick functionality available for API >= 12 devices */
 class SDLJoystickHandler_API12 extends SDLJoystickHandler {
   
     class SDLJoystick {
-        public int id;
+        public int device_id;
         public String name;
         public ArrayList<InputDevice.MotionRange> axes;
     }
@@ -953,54 +925,72 @@
     private ArrayList<SDLJoystick> mJoysticks;
     
     public SDLJoystickHandler_API12() {
-        /* FIXME: Move the joystick initialization code to its own function and support hotplugging of devices */
        
         mJoysticks = new ArrayList<SDLJoystick>();
-        
+    }
+
+    @Override
+    public void pollInputDevices() {
         int[] deviceIds = InputDevice.getDeviceIds();
-        for(int i=0; i<deviceIds.length; i++) {
-            SDLJoystick joystick = new SDLJoystick();
-            InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
-            
-            if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                joystick.id = deviceIds[i];
-                joystick.name = joystickDevice.getName();
-                joystick.axes = new ArrayList<InputDevice.MotionRange>();
-                
-                for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) {
-                     if ( (range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                        joystick.axes.add(range);
-                     }
+        // 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( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+                    joystick.device_id = deviceIds[i];
+                    joystick.name = joystickDevice.getName();
+                    joystick.axes = new ArrayList<InputDevice.MotionRange>();
+                    
+                    for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) {
+                         if ( (range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+                            joystick.axes.add(range);
+                         }
+                    }
+                    
+                    mJoysticks.add(joystick);
+                    SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1, joystick.axes.size(), 0, 0);
                 }
-                
-                mJoysticks.add(joystick);
             }
         }
-    }
-    
-    @Override
-    public int getNumJoysticks() {
-        return mJoysticks.size();
-    }
-    
-    @Override
-    public String getJoystickName(int joy) {
-        return mJoysticks.get(joy).name;
+        
+        /* Check removed devices */
+        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
+        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(device_id);
+            }
+        }
+            
+        for(int i=0; i < removedDevices.size(); i++) {
+            int device_id = removedDevices.get(i);
+            SDLActivity.nativeRemoveJoystick(device_id);
+            for (int j=0; j < mJoysticks.size(); j++) {
+                if (mJoysticks.get(j).device_id == device_id) {
+                    mJoysticks.remove(j);
+                    break;
+                }
+            }
+        }        
     }
     
-    @Override
-    public int getJoystickAxes(int joy) {
-        return mJoysticks.get(joy).axes.size();
-    }
-    
-    @Override
-    public int getJoyId(int devId) {
+    protected SDLJoystick getJoystick(int device_id) {
         for(int i=0; i < mJoysticks.size(); i++) {
-            if (mJoysticks.get(i).id == devId) {
-                return i;
+            if (mJoysticks.get(i).device_id == device_id) {
+                return mJoysticks.get(i);
             }
         }
-        return -1;
+        return null;
     }   
     
     @Override        
@@ -1010,14 +1000,13 @@
             int action = event.getActionMasked();
             switch(action) {
                 case MotionEvent.ACTION_MOVE:
-                    int id = getJoyId( event.getDeviceId() );
-                    if ( id != -1 ) {
-                        SDLJoystick joystick = mJoysticks.get(id);
+                    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;
-                            SDLActivity.onNativeJoy(id, i, value );
+                            SDLActivity.onNativeJoy(joystick.device_id, i, value );
                         }                       
                     }
                     break;
--- a/src/core/android/SDL_android.c	Mon Dec 09 15:17:20 2013 -0500
+++ b/src/core/android/SDL_android.c	Tue Dec 10 16:24:11 2013 -0300
@@ -34,7 +34,7 @@
 #include "../../video/android/SDL_androidtouch.h"
 #include "../../video/android/SDL_androidvideo.h"
 #include "../../video/android/SDL_androidwindow.h"
-#include "../../joystick/android/SDL_sysjoystick.h"
+#include "../../joystick/android/SDL_sysjoystick_c.h"
 
 #include <android/log.h>
 #include <pthread.h>
@@ -75,6 +75,7 @@
 static jmethodID midAudioWriteShortBuffer;
 static jmethodID midAudioWriteByteBuffer;
 static jmethodID midAudioQuit;
+static jmethodID midPollInputDevices;
 
 /* Accelerometer data storage */
 static float fLastAccelerometer[3];
@@ -127,11 +128,13 @@
                                 "audioWriteByteBuffer", "([B)V");
     midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
                                 "audioQuit", "()V");
+    midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+                                "pollInputDevices", "()V");
 
     bHasNewData = false;
 
     if(!midGetNativeSurface || !midFlipBuffers || !midAudioInit ||
-       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
+       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit || !midPollInputDevices) {
         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
     }
     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
@@ -148,25 +151,47 @@
 // Paddown
 int Java_org_libsdl_app_SDLActivity_onNativePadDown(
                                     JNIEnv* env, jclass jcls,
-                                    jint padId, jint keycode)
+                                    jint device_id, jint keycode)
 {
-    return Android_OnPadDown(padId, keycode);
+    return Android_OnPadDown(device_id, keycode);
 }
 
 // Padup
 int Java_org_libsdl_app_SDLActivity_onNativePadUp(
                                    JNIEnv* env, jclass jcls,
-                                   jint padId, jint keycode)
+                                   jint device_id, jint keycode)
 {
-    return Android_OnPadUp(padId, keycode);
+    return Android_OnPadUp(device_id, keycode);
 }
 
 /* Joy */
 void Java_org_libsdl_app_SDLActivity_onNativeJoy(
                                     JNIEnv* env, jclass jcls,
-                                    jint joyId, jint axis, jfloat value)
+                                    jint device_id, jint axis, jfloat value)
+{
+    Android_OnJoy(device_id, axis, value);
+}
+
+
+int Java_org_libsdl_app_SDLActivity_nativeAddJoystick(
+    JNIEnv* env, jclass jcls,
+    jint device_id, jstring device_name, jint is_accelerometer, 
+    jint nbuttons, jint naxes, jint nhats, jint nballs)
 {
-    Android_OnJoy(joyId, axis, value);
+    int retval;
+    const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
+
+    retval = Android_AddJoystick(device_id, name, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs);
+
+    (*env)->ReleaseStringUTFChars(env, device_name, name);
+    
+    return retval;
+}
+
+int Java_org_libsdl_app_SDLActivity_nativeRemoveJoystick(
+    JNIEnv* env, jclass jcls, jint device_id)
+{
+    return Android_RemoveJoystick(device_id);
 }
 
 
@@ -1247,62 +1272,12 @@
     return number;
 }
 
-/* return the total number of plugged in joysticks */
-int Android_JNI_GetNumJoysticks()
-{
-    JNIEnv* env = Android_JNI_GetEnv();
-    if (!env) {
-        return -1;
-    }
-    
-    jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "getNumJoysticks", "()I");
-    if (!mid) {
-        return -1;
-    }
-    
-    return (int)(*env)->CallStaticIntMethod(env, mActivityClass, mid);
-}
-
-/* Return the name of joystick number "i" */
-char* Android_JNI_GetJoystickName(int i)
+void Android_JNI_PollInputDevices()
 {
-    JNIEnv* env = Android_JNI_GetEnv();
-    if (!env) {
-        return SDL_strdup("");
-    }
-    
-    jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "getJoystickName", "(I)Ljava/lang/String;");
-    if (!mid) {
-        return SDL_strdup("");
-    }
-    jstring string = (jstring)((*env)->CallStaticObjectMethod(env, mActivityClass, mid, i));
-    const char* utf = (*env)->GetStringUTFChars(env, string, 0);
-    if (!utf) {
-        return SDL_strdup("");
-    }
-    
-    char* text = SDL_strdup(utf);
-    (*env)->ReleaseStringUTFChars(env, string, utf);
-    return text;
+    JNIEnv *env = Android_JNI_GetEnv();
+    (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);    
 }
 
-/* return the number of axes in the given joystick */
-int Android_JNI_GetJoystickAxes(int joy)
-{
-    JNIEnv* env = Android_JNI_GetEnv();
-    if (!env) {
-        return -1;
-    }
-    
-    jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "getJoystickAxes", "(I)I");
-    if (!mid) {
-        return -1;
-    }
-    
-    return (int)(*env)->CallIntMethod(env, mActivityClass, mid, joy);
-}
-
-
 /* sends message to be handled on the UI event dispatch thread */
 int Android_JNI_SendMessage(int command, int param)
 {
--- a/src/core/android/SDL_android.h	Mon Dec 09 15:17:20 2013 -0500
+++ b/src/core/android/SDL_android.h	Tue Dec 10 16:24:11 2013 -0300
@@ -66,9 +66,8 @@
 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent);
     
 /* Joystick support */
-int Android_JNI_GetNumJoysticks();
-char* Android_JNI_GetJoystickName(int i);
-int Android_JNI_GetJoystickAxes(int joy);
+void Android_JNI_PollInputDevices();
+
 
 /* Touch support */
 int Android_JNI_GetTouchDeviceIds(int **ids);
--- a/src/joystick/android/SDL_sysjoystick.c	Mon Dec 09 15:17:20 2013 -0500
+++ b/src/joystick/android/SDL_sysjoystick.c	Tue Dec 10 16:24:11 2013 -0300
@@ -23,15 +23,18 @@
 
 #ifdef SDL_JOYSTICK_ANDROID
 
-/* This is the system specific header for the SDL joystick API */
 #include <stdio.h>              /* For the definition of NULL */
-
 #include "SDL_error.h"
 #include "SDL_events.h"
+
+#if !SDL_EVENTS_DISABLED
+#include "../../events/SDL_events_c.h"
+#endif
+
 #include "SDL_joystick.h"
 #include "SDL_hints.h"
 #include "SDL_assert.h"
-#include "../SDL_sysjoystick.h"
+#include "SDL_sysjoystick_c.h"
 #include "../SDL_joystick_c.h"
 #include "../../core/android/SDL_android.h"
 
@@ -57,14 +60,17 @@
 #define AKEYCODE_BUTTON_16 203
 #endif
 
-#define ANDROID_ACCELEROMETER_INDEX (SYS_numjoysticks - 1)
 #define ANDROID_ACCELEROMETER_NAME "Android Accelerometer"
+#define ANDROID_ACCELEROMETER_DEVICE_ID INT_MIN
 #define ANDROID_MAX_NBUTTONS 36
 
-static SDL_Joystick **SYS_Joysticks;
-static char **SYS_JoystickNames;
-static int SYS_numjoysticks;
-static SDL_bool SYS_accelAsJoy;
+static SDL_joylist_item * JoystickByDeviceId(int device_id);
+
+static SDL_joylist_item *SDL_joylist = NULL;
+static SDL_joylist_item *SDL_joylist_tail = NULL;
+static int numjoysticks = 0;
+static int instance_counter = 0;
+
 
 /* Function to convert Android keyCodes into SDL ones.
  * This code manipulation is done to get a sequential list of codes.
@@ -139,78 +145,265 @@
     
 }
 
-/* Function to scan the system for joysticks.
- * This function should set SDL_numjoysticks to the number of available
- * joysticks.  Joystick 0 should be the system default joystick.
- * It should return 0, or -1 on an unrecoverable fatal error.
- */
+int
+Android_OnPadDown(int device_id, int keycode)
+{
+    SDL_joylist_item *item;
+    int button = keycode_to_SDL(keycode);
+    if (button >= 0) {
+        item = JoystickByDeviceId(device_id);
+        if (item && item->joystick) {
+            SDL_PrivateJoystickButton(item->joystick, button , SDL_PRESSED);
+        }
+        return 0;
+    }
+    
+    return -1;
+}
+
+int
+Android_OnPadUp(int device_id, int keycode)
+{
+    SDL_joylist_item *item;
+    int button = keycode_to_SDL(keycode);
+    if (button >= 0) {
+        item = JoystickByDeviceId(device_id);
+        if (item && item->joystick) {
+            SDL_PrivateJoystickButton(item->joystick, button, SDL_RELEASED);
+        }
+        return 0;
+    }
+    
+    return -1;
+}
+
+int
+Android_OnJoy(int device_id, int axis, float value)
+{
+    /* Android gives joy info normalized as [-1.0, 1.0] or [0.0, 1.0] */
+    SDL_joylist_item *item = JoystickByDeviceId(device_id);
+    if (item && item->joystick) {
+        SDL_PrivateJoystickAxis(item->joystick, axis, (Sint16) (32767.*value) );
+    }
+    
+    return 0;
+}
+
+
+int
+Android_AddJoystick(int device_id, const char *name, SDL_bool is_accelerometer, int nbuttons, int naxes, int nhats, int nballs)
+{
+    SDL_JoystickGUID guid;
+    SDL_joylist_item *item;
+#if !SDL_EVENTS_DISABLED
+    SDL_Event event;
+#endif
+    
+    if(JoystickByDeviceId(device_id) != NULL || name == NULL) {
+        return -1;
+    }
+    
+    /* the GUID is just the first 16 chars of the name for now */
+    SDL_zero( guid );
+    SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
+
+    item = (SDL_joylist_item *) SDL_malloc(sizeof (SDL_joylist_item));
+    if (item == NULL) {
+        return -1;
+    }
+
+    SDL_zerop(item);
+    item->guid = guid;
+    item->device_id = device_id;
+    item->name = SDL_strdup(name);
+    if ( item->name == NULL ) {
+         SDL_free(item);
+         return -1;
+    }
+    
+    item->is_accelerometer = is_accelerometer;
+    if (nbuttons > -1) {
+        item->nbuttons = nbuttons;
+    }
+    else {
+        item->nbuttons = ANDROID_MAX_NBUTTONS;
+    }
+    item->naxes = naxes;
+    item->nhats = nhats;
+    item->nballs = nballs;
+    item->device_instance = instance_counter++;
+    if (SDL_joylist_tail == NULL) {
+        SDL_joylist = SDL_joylist_tail = item;
+    } else {
+        SDL_joylist_tail->next = item;
+        SDL_joylist_tail = item;
+    }
+
+    /* Need to increment the joystick count before we post the event */
+    ++numjoysticks;
+
+#if !SDL_EVENTS_DISABLED
+    event.type = SDL_JOYDEVICEADDED;
+
+    if (SDL_GetEventState(event.type) == SDL_ENABLE) {
+        event.jdevice.which = (numjoysticks - 1);
+        if ( (SDL_EventOK == NULL) ||
+             (*SDL_EventOK) (SDL_EventOKParam, &event) ) {
+            SDL_PushEvent(&event);
+        }
+    }
+#endif /* !SDL_EVENTS_DISABLED */
+
+    SDL_Log("Added joystick %s with device_id %d", name, device_id);
+
+    return numjoysticks;
+}
+
+int 
+Android_RemoveJoystick(int device_id)
+{
+    SDL_joylist_item *item = SDL_joylist;
+    SDL_joylist_item *prev = NULL;
+#if !SDL_EVENTS_DISABLED
+    SDL_Event event;
+#endif
+    
+    /* Don't call JoystickByDeviceId here or there'll be an infinite loop! */
+    while (item != NULL) {
+        if (item->device_id == device_id) {
+            break;
+        }
+        prev = item;
+        item = item->next;
+    }
+    
+    if (item == NULL) {
+        return -1;
+    }
+
+    const int retval = item->device_instance;
+    if (prev != NULL) {
+        prev->next = item->next;
+    } else {
+        SDL_assert(SDL_joylist == item);
+        SDL_joylist = item->next;
+    }
+    if (item == SDL_joylist_tail) {
+        SDL_joylist_tail = prev;
+    }
+
+    /* Need to decrement the joystick count before we post the event */
+    --numjoysticks;
+
+#if !SDL_EVENTS_DISABLED
+    event.type = SDL_JOYDEVICEREMOVED;
+
+    if (SDL_GetEventState(event.type) == SDL_ENABLE) {
+        event.jdevice.which = item->device_instance;
+        if ( (SDL_EventOK == NULL) ||
+             (*SDL_EventOK) (SDL_EventOKParam, &event) ) {
+            SDL_PushEvent(&event);
+        }
+    }
+#endif /* !SDL_EVENTS_DISABLED */
+
+    SDL_Log("Removed joystick with device_id %d", device_id);
+    
+    SDL_free(item->name);
+    SDL_free(item);
+    return retval;
+}
+
+
 int
 SDL_SYS_JoystickInit(void)
 {
-    int i = 0;
     const char *env;
+    SDL_SYS_JoystickDetect();
     
     env = SDL_GetHint(SDL_HINT_ACCEL_AS_JOY);
-    if (env && !SDL_atoi(env))
-        SYS_accelAsJoy = SDL_FALSE;
-    else
-        SYS_accelAsJoy = SDL_TRUE; /* Default behavior */
-    
-    SYS_numjoysticks = Android_JNI_GetNumJoysticks();
-    if (SYS_accelAsJoy) {
-        SYS_numjoysticks++;
-    }
-    SYS_Joysticks = (SDL_Joystick **)SDL_calloc(1, SYS_numjoysticks*sizeof(SDL_Joystick *));
-    if (SYS_Joysticks == NULL)
-    {
-        return SDL_OutOfMemory();
-    }
-    SYS_JoystickNames = (char **)SDL_calloc(1, SYS_numjoysticks*sizeof(char *));
-    if (SYS_JoystickNames == NULL)
-    {
-        SDL_free(SYS_Joysticks);
-        SYS_Joysticks = NULL;
-        return SDL_OutOfMemory();
-    }
-    
-    for (i = 0; i < SYS_numjoysticks; i++)
-    {
-        if ( SYS_accelAsJoy && i == ANDROID_ACCELEROMETER_INDEX ) {
-            SYS_JoystickNames[i] = ANDROID_ACCELEROMETER_NAME;
-        } else {
-            SYS_JoystickNames[i] = Android_JNI_GetJoystickName(i);
-        }
+    if (!env || SDL_atoi(env)) {
+        /* Default behavior, accelerometer as joystick */
+        Android_AddJoystick(ANDROID_ACCELEROMETER_DEVICE_ID, ANDROID_ACCELEROMETER_NAME, SDL_TRUE, 0, 3, 0, 0);
     }
    
-    return (SYS_numjoysticks);
+    return (numjoysticks);
+
 }
 
 int SDL_SYS_NumJoysticks()
 {
-    return SYS_numjoysticks;
+    return numjoysticks;
 }
 
 void SDL_SYS_JoystickDetect()
 {
+    /* Support for device connect/disconnect is API >= 16 only,
+     * so we have to poll ever few seconds.
+     * Ref: http://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
+     */
+    Android_JNI_PollInputDevices();   
+}
+
+SDL_bool SDL_SYS_JoystickNeedsPolling()
+{
+    return SDL_TRUE;
 }
 
-/* TODO: Hotplugging support */
-SDL_bool SDL_SYS_JoystickNeedsPolling()
+static SDL_joylist_item *
+JoystickByDevIndex(int device_index)
+{
+    SDL_joylist_item *item = SDL_joylist;
+
+    if ((device_index < 0) || (device_index >= numjoysticks)) {
+        return NULL;
+    }
+
+    while (device_index > 0) {
+        SDL_assert(item != NULL);
+        device_index--;
+        item = item->next;
+    }
+
+    return item;
+}
+
+static SDL_joylist_item *
+JoystickByDeviceId(int device_id)
 {
-    return SDL_FALSE;
+    SDL_joylist_item *item = SDL_joylist;
+
+    while (item != NULL) {
+        if (item->device_id == device_id) {
+            return item;
+        }
+        item = item->next;
+    }
+    
+    /* Joystick not found, try adding it */
+    SDL_SYS_JoystickDetect();
+    
+    while (item != NULL) {
+        if (item->device_id == device_id) {
+            return item;
+        }
+        item = item->next;
+    }
+
+    return NULL;
 }
 
 /* Function to get the device-dependent name of a joystick */
 const char *
 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
 {
-    return SYS_JoystickNames[device_index];
+    return JoystickByDevIndex(device_index)->name;
 }
 
 /* Function to perform the mapping from device index to the instance id for this index */
 SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
 {
-    return device_index;
+    return JoystickByDevIndex(device_index)->device_instance;
 }
 
 /* Function to open a joystick for use.
@@ -221,49 +414,54 @@
 int
 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
-    if (device_index < SYS_numjoysticks) {
-        joystick->nhats = 0;
-        joystick->nballs = 0;
-        if (SYS_accelAsJoy && device_index == ANDROID_ACCELEROMETER_INDEX) {
-            joystick->nbuttons = 0;
-            joystick->naxes = 3;
-        } else {
-            /* FIXME: Get the real number of buttons in the device? */
-            joystick->nbuttons = ANDROID_MAX_NBUTTONS;
-            joystick->naxes = Android_JNI_GetJoystickAxes(device_index);
-        }
-        
-        SYS_Joysticks[device_index] = joystick;
-        return 0;
-    } else {
-        return SDL_SetError("No joystick available with that index");
+    SDL_joylist_item *item = JoystickByDevIndex(device_index);
+    char *fname = NULL;
+
+    if (item == NULL ) {
+        return SDL_SetError("No such device");
+    }
+    
+    if (item->joystick != NULL) {
+        return SDL_SetError("Joystick already opened");
     }
+
+    joystick->instance_id = item->device_instance;
+    joystick->hwdata = (struct joystick_hwdata *) item;
+    item->joystick = joystick;
+    joystick->nhats = item->nhats;
+    joystick->nballs = item->nballs;
+    joystick->nbuttons = item->nbuttons;
+    joystick->naxes = item->naxes;
+
+    return (0);
 }
 
 /* Function to determine is this joystick is attached to the system right now */
 SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
 {
-    return SDL_TRUE;
+    return !joystick->closed && (joystick->hwdata != NULL);
 }
 
-/* Function to update the state of a joystick - called as a device poll.
- * This function shouldn't update the joystick structure directly,
- * but instead should call SDL_PrivateJoystick*() to deliver events
- * and update joystick device state.
- */
 void
 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
 {
     int i;
     Sint16 value;
     float values[3];
+    SDL_joylist_item *item = SDL_joylist;
 
-    if (SYS_accelAsJoy && Android_JNI_GetAccelerometerValues(values) &&
-        joystick->instance_id == ANDROID_ACCELEROMETER_INDEX) {
-        for ( i = 0; i < 3; i++ ) {
-            value = (Sint16)(values[i] * 32767.0f);
-            SDL_PrivateJoystickAxis(joystick, i, value);
+    while (item) {
+        if (item->is_accelerometer) {
+            if (item->joystick) {
+                Android_JNI_GetAccelerometerValues(values);
+                for ( i = 0; i < 3; i++ ) {
+                    value = (Sint16)(values[i] * 32767.0f);
+                    SDL_PrivateJoystickAxis(item->joystick, i, value);
+                }
+            }
+            break;
         }
+        item = item->next;
     }
 }
 
@@ -271,14 +469,10 @@
 void
 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
 {
-    int device_index;
-    
-    for (device_index = 0; device_index < SYS_numjoysticks; device_index++) {
-        if ( SYS_Joysticks[device_index] == joystick ) {
-            SYS_Joysticks[device_index] = NULL;
-        }
+    if (joystick->hwdata) {
+        ((SDL_joylist_item*)joystick->hwdata)->joystick = NULL;
+        joystick->hwdata = NULL;
     }
-    
     joystick->closed = 1;
 }
 
@@ -286,70 +480,29 @@
 void
 SDL_SYS_JoystickQuit(void)
 {
-    SDL_free(SYS_JoystickNames);
-    SDL_free(SYS_Joysticks);
-    SYS_JoystickNames = NULL;
-    SYS_Joysticks = NULL;
+    SDL_joylist_item *item = NULL;
+    SDL_joylist_item *next = NULL;
+
+    for (item = SDL_joylist; item; item = next) {
+        next = item->next;
+        SDL_free(item->name);
+        SDL_free(item);
+    }
+
+    SDL_joylist = SDL_joylist_tail = NULL;
+
+    numjoysticks = 0;
+    instance_counter = 0;
 }
 
 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
 {
-    SDL_JoystickGUID guid;
-    /* the GUID is just the first 16 chars of the name for now */
-    const char *name = SDL_SYS_JoystickNameForDeviceIndex( device_index );
-    SDL_zero( guid );
-    SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
-    return guid;
+    return JoystickByDevIndex(device_index)->guid;
 }
 
 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
 {
-    SDL_JoystickGUID guid;
-    /* the GUID is just the first 16 chars of the name for now */
-    const char *name = joystick->name;
-    SDL_zero( guid );
-    SDL_memcpy( &guid, name, SDL_min( sizeof(guid), SDL_strlen( name ) ) );
-    return guid;
-}
-
-int
-Android_OnPadDown(int padId, int keycode)
-{
-    int button = keycode_to_SDL(keycode);
-    if (button >= 0) {
-        if (SYS_Joysticks[padId]) {
-            SDL_PrivateJoystickButton(SYS_Joysticks[padId], button , SDL_PRESSED);
-        }
-        return 0;
-    }
-    
-    return -1;
-}
-
-int
-Android_OnPadUp(int padId, int keycode)
-{
-    int button = keycode_to_SDL(keycode);
-    if (button >= 0) {
-        if (SYS_Joysticks[padId]) {
-            SDL_PrivateJoystickButton(SYS_Joysticks[padId], button, SDL_RELEASED);
-        }
-        return 0;
-    }
-    
-    return -1;
-}
-
-int
-Android_OnJoy(int joyId, int axis, float value)
-{
-    /* Android gives joy info normalized as [-1.0, 1.0] or [0.0, 1.0] */
-    /* TODO: Are the reported values right? */
-    if (SYS_Joysticks[joyId]) {
-        SDL_PrivateJoystickAxis(SYS_Joysticks[joyId], axis, (Sint16) (32767.*value) );
-    }
-    
-    return 0;
+    return ((SDL_joylist_item*)joystick->hwdata)->guid;
 }
 
 #endif /* SDL_JOYSTICK_ANDROID */
--- a/src/joystick/android/SDL_sysjoystick.h	Mon Dec 09 15:17:20 2013 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-/*
- Simple DirectMedia Layer
- Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
- 
- This software is provided 'as-is', without any express or implied
- warranty.  In no event will the authors be held liable for any damages
- arising from the use of this software.
- 
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
-
-#include "SDL_config.h"
-
-extern int Android_OnPadDown(int padId, int keycode);
-extern int Android_OnPadUp(int padId, int keycode);
-extern int Android_OnJoy(int joyId, int axisnum, float value);
-
-/* vi: set ts=4 sw=4 expandtab: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/joystick/android/SDL_sysjoystick_c.h	Tue Dec 10 16:24:11 2013 -0300
@@ -0,0 +1,51 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
+ 
+ This software is provided 'as-is', without any express or implied
+ warranty.  In no event will the authors be held liable for any damages
+ arising from the use of this software.
+ 
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ 
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "SDL_config.h"
+
+#ifdef SDL_JOYSTICK_ANDROID
+#include "../SDL_sysjoystick.h"
+
+extern int Android_OnPadDown(int device_id, int keycode);
+extern int Android_OnPadUp(int device_id, int keycode);
+extern int Android_OnJoy(int device_id, int axisnum, float value);
+extern int Android_AddJoystick(int device_id, const char *name, SDL_bool is_accelerometer, int nbuttons, int naxes, int nhats, int nballs);
+extern int Android_RemoveJoystick(int device_id);
+
+/* A linked list of available joysticks */
+typedef struct SDL_joylist_item
+{
+    int device_instance;
+    int device_id; /* Android's device id */
+    char *name;   /* "SideWinder 3D Pro" or whatever */
+    SDL_JoystickGUID guid;
+    SDL_bool is_accelerometer;
+    SDL_Joystick *joystick;
+    int nbuttons, naxes, nhats, nballs;
+    
+    struct SDL_joylist_item *next;
+} SDL_joylist_item;
+
+typedef SDL_joylist_item joystick_hwdata;
+
+#endif /* SDL_JOYSTICK_ANDROID */
+
+/* vi: set ts=4 sw=4 expandtab: */