Fixed bug 1700 - Joysticks not supported in Android
authorSam Lantinga <slouken@libsdl.org>
Tue, 19 Mar 2013 23:03:57 -0700
changeset 70189cef1005df5f
parent 7017 7c2eb015a6d7
child 7019 a713101e1d25
Fixed bug 1700 - Joysticks not supported in Android
android-project/project.properties
android-project/src/org/libsdl/app/SDLActivity.java
src/core/android/SDL_android.cpp
src/core/android/SDL_android.h
     1.1 --- a/android-project/project.properties	Tue Mar 19 22:25:02 2013 -0700
     1.2 +++ b/android-project/project.properties	Tue Mar 19 23:03:57 2013 -0700
     1.3 @@ -11,4 +11,4 @@
     1.4  #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
     1.5  
     1.6  # Project target.
     1.7 -target=android-10
     1.8 +target=android-12
     2.1 --- a/android-project/src/org/libsdl/app/SDLActivity.java	Tue Mar 19 22:25:02 2013 -0700
     2.2 +++ b/android-project/src/org/libsdl/app/SDLActivity.java	Tue Mar 19 23:03:57 2013 -0700
     2.3 @@ -24,6 +24,8 @@
     2.4  import android.content.*;
     2.5  
     2.6  import java.lang.*;
     2.7 +import java.util.List;
     2.8 +import java.util.ArrayList;
     2.9  
    2.10  
    2.11  /**
    2.12 @@ -42,6 +44,11 @@
    2.13  
    2.14      // This is what SDL runs in. It invokes SDL_main(), eventually
    2.15      private static Thread mSDLThread;
    2.16 +    
    2.17 +    // Joystick
    2.18 +    private static List<Integer> mJoyIdList;
    2.19 +    // TODO: Have a (somewhat) more efficient way of storing these?
    2.20 +    private static List<List<Integer>> mJoyAxesLists;
    2.21  
    2.22      // Audio
    2.23      private static Thread mAudioThread;
    2.24 @@ -156,12 +163,15 @@
    2.25      public static native void nativePause();
    2.26      public static native void nativeResume();
    2.27      public static native void onNativeResize(int x, int y, int format);
    2.28 +    public static native void onNativePadDown(int padId, int keycode);
    2.29 +    public static native void onNativePadUp(int padId, int keycode);
    2.30 +    public static native void onNativeJoy(int joyId, int axisNum, float value);
    2.31      public static native void onNativeKeyDown(int keycode);
    2.32      public static native void onNativeKeyUp(int keycode);
    2.33      public static native void onNativeTouch(int touchDevId, int pointerFingerId,
    2.34                                              int action, float x, 
    2.35                                              float y, float p);
    2.36 -    public static native void onNativeAccel(float x, float y, float z);
    2.37 +//  public static native void onNativeAccel(float x, float y, float z);
    2.38      public static native void nativeRunAudioThread();
    2.39  
    2.40  
    2.41 @@ -180,6 +190,74 @@
    2.42          mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
    2.43      }
    2.44  
    2.45 +    // Call when initializing the joystick subsystem
    2.46 +    public static void joystickInit() {
    2.47 +        mJoyIdList = new ArrayList<Integer>();
    2.48 +        mJoyAxesLists = new ArrayList<List<Integer>>();
    2.49 +
    2.50 +        int[] deviceIds = InputDevice.getDeviceIds();
    2.51 +        for(int i=0; i<deviceIds.length; i++) {
    2.52 +            if( (InputDevice.getDevice(deviceIds[i]).getSources() & 0x00000010 /* API 12: InputDevice.SOURCE_CLASS_JOYSTICK*/) != 0) {
    2.53 +                mJoyIdList.add(deviceIds[i]);
    2.54 +                List<Integer> axesList = new ArrayList<Integer>();
    2.55 +                /* With API 12 and above we can get a list of all motion
    2.56 +                 * ranges, hence all axes. Some of them may be irrelevant
    2.57 +                 * (e.g. an embedded trackpad). We filter the desired axes.
    2.58 +                 */
    2.59 +                if(Build.VERSION.SDK_INT >= 12) {
    2.60 +                     List<InputDevice.MotionRange> rangesList = InputDevice.getDevice(deviceIds[i]).getMotionRanges();
    2.61 +                     for (InputDevice.MotionRange range : rangesList) {
    2.62 +                         // Skip any possibly unrelated axis
    2.63 +                         if ( (range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
    2.64 +                             axesList.add(range.getAxis());
    2.65 +                         }
    2.66 +                     }
    2.67 +                } else {
    2.68 +                    // In older versions, we can assume a sane X-Y default configuration
    2.69 +                    axesList.add(MotionEvent.AXIS_X);
    2.70 +                    axesList.add(MotionEvent.AXIS_Y);
    2.71 +                }
    2.72 +                mJoyAxesLists.add(axesList);
    2.73 +            }
    2.74 +        }
    2.75 +    }
    2.76 +
    2.77 +    // Call when one clears joystick subsystem resources
    2.78 +    public static void joystickQuit() {
    2.79 +        mJoyIdList = null;
    2.80 +        mJoyAxesLists = null;
    2.81 +    }
    2.82 +
    2.83 +    public static int getNumJoysticks() {
    2.84 +        if (mJoyIdList == null)
    2.85 +            return -1;
    2.86 +        return mJoyIdList.size();
    2.87 +    }
    2.88 +
    2.89 +    public static String getJoystickName(int joy) {
    2.90 +        if (mJoyIdList == null)
    2.91 +            return null;
    2.92 +        return InputDevice.getDevice(mJoyIdList.get(joy)).getName();
    2.93 +    }
    2.94 +
    2.95 +    public static List<Integer> getJoystickAxesList(int joy) {
    2.96 +        if (mJoyIdList == null)
    2.97 +            return null;
    2.98 +        return mJoyAxesLists.get(joy);
    2.99 +    }
   2.100 +
   2.101 +    public static int getJoystickNumOfAxes(int joy) {
   2.102 +        if (mJoyIdList == null)
   2.103 +            return -1;
   2.104 +        return mJoyAxesLists.get(joy).size();
   2.105 +    }
   2.106 +
   2.107 +    public static int getJoyId(int devId) {
   2.108 +        if (mJoyIdList == null)
   2.109 +            return -1;
   2.110 +        return mJoyIdList.indexOf(devId);
   2.111 +    }
   2.112 +
   2.113      public static void sendMessage(int command, int param) {
   2.114          mSingleton.sendCommand(command, Integer.valueOf(param));
   2.115      }
   2.116 @@ -478,7 +556,12 @@
   2.117          setFocusableInTouchMode(true);
   2.118          requestFocus();
   2.119          setOnKeyListener(this); 
   2.120 -        setOnTouchListener(this);   
   2.121 +        setOnTouchListener(this);
   2.122 +
   2.123 +        // Listen to joystick motion events if supported
   2.124 +        if (Build.VERSION.SDK_INT >= 12) {
   2.125 +            setOnGenericMotionListener(new SDLOnGenericMotionListener());
   2.126 +        }
   2.127  
   2.128          mSensorManager = (SensorManager)context.getSystemService("sensor");
   2.129  
   2.130 @@ -568,18 +651,65 @@
   2.131  
   2.132  
   2.133  
   2.134 +    // Listen to joystick motion events if supported (API >= 12)
   2.135 +    private static class SDLOnGenericMotionListener implements View.OnGenericMotionListener {
   2.136 +        @Override
   2.137 +        public boolean onGenericMotion(View view, MotionEvent event) {
   2.138 +            int actionPointerIndex = event.getActionIndex();
   2.139 +            int action = event.getActionMasked();
   2.140 +
   2.141 +            if ( (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
   2.142 +                switch(action) {
   2.143 +                    case MotionEvent.ACTION_MOVE:
   2.144 +                        int id = SDLActivity.getJoyId( event.getDeviceId() );
   2.145 +                        // The joystick subsystem may be uninitialized, so ignore
   2.146 +                        if (id < 0)
   2.147 +                            return true;
   2.148 +                        // Update values for all joystick axes
   2.149 +                        List<Integer> axes = SDLActivity.getJoystickAxesList(id);
   2.150 +                        for (int axisIndex = 0; axisIndex < axes.size(); axisIndex++) {
   2.151 +                            SDLActivity.onNativeJoy(id, axisIndex, event.getAxisValue(axes.get(axisIndex), actionPointerIndex));
   2.152 +                        }
   2.153 +
   2.154 +                        return true;
   2.155 +                }
   2.156 +            }
   2.157 +            return false;
   2.158 +        }
   2.159 +    }
   2.160 +
   2.161      // Key events
   2.162      public boolean onKey(View  v, int keyCode, KeyEvent event) {
   2.163 -
   2.164 -        if (event.getAction() == KeyEvent.ACTION_DOWN) {
   2.165 -            //Log.v("SDL", "key down: " + keyCode);
   2.166 -            SDLActivity.onNativeKeyDown(keyCode);
   2.167 -            return true;
   2.168 -        }
   2.169 -        else if (event.getAction() == KeyEvent.ACTION_UP) {
   2.170 -            //Log.v("SDL", "key up: " + keyCode);
   2.171 -            SDLActivity.onNativeKeyUp(keyCode);
   2.172 -            return true;
   2.173 +        /* Dispatch the different events depending on where they come from:
   2.174 +         * If the input device has some joystick source (probably differing
   2.175 +         * from the source to which the given key belongs), assume it is a
   2.176 +         * game controller button. Otherwise, assume a keyboard key.
   2.177 +         * This should also take care of some kinds of manually toggled soft
   2.178 +         * keyboards (i.e. not via the SDL text input API).
   2.179 +         */
   2.180 +        if ( (event.getDevice().getSources() & 0x00000010 /* API 12: InputDevice.SOURCE_CLASS_JOYSTICK*/) != 0) {
   2.181 +            int id = SDLActivity.getJoyId( event.getDeviceId() );
   2.182 +            // The joystick subsystem may be uninitialized, so ignore
   2.183 +            if (id < 0)
   2.184 +                return true;
   2.185 +            if (event.getAction() == KeyEvent.ACTION_DOWN) {
   2.186 +                SDLActivity.onNativePadDown(id, keyCode);
   2.187 +                return true;
   2.188 +            } else if (event.getAction() == KeyEvent.ACTION_UP) {
   2.189 +                SDLActivity.onNativePadUp(id, keyCode);
   2.190 +                return true;
   2.191 +            }
   2.192 +        } else {
   2.193 +            if (event.getAction() == KeyEvent.ACTION_DOWN) {
   2.194 +                //Log.v("SDL", "key down: " + keyCode);
   2.195 +                SDLActivity.onNativeKeyDown(keyCode);
   2.196 +                return true;
   2.197 +            }
   2.198 +            else if (event.getAction() == KeyEvent.ACTION_UP) {
   2.199 +                //Log.v("SDL", "key up: " + keyCode);
   2.200 +                SDLActivity.onNativeKeyUp(keyCode);
   2.201 +                return true;
   2.202 +            }
   2.203          }
   2.204          
   2.205          return false;
   2.206 @@ -614,7 +744,7 @@
   2.207               }
   2.208          }
   2.209        return true;
   2.210 -   } 
   2.211 +   }
   2.212  
   2.213      // Sensor events
   2.214      public void enableSensor(int sensortype, boolean enabled) {
   2.215 @@ -634,13 +764,14 @@
   2.216      }
   2.217  
   2.218      public void onSensorChanged(SensorEvent event) {
   2.219 +/*
   2.220          if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
   2.221              SDLActivity.onNativeAccel(event.values[0] / SensorManager.GRAVITY_EARTH,
   2.222                                        event.values[1] / SensorManager.GRAVITY_EARTH,
   2.223                                        event.values[2] / SensorManager.GRAVITY_EARTH);
   2.224          }
   2.225 +*/
   2.226      }
   2.227 -    
   2.228  }
   2.229  
   2.230  /* This is a fake invisible editor view that receives the input and defines the
     3.1 --- a/src/core/android/SDL_android.cpp	Tue Mar 19 22:25:02 2013 -0700
     3.2 +++ b/src/core/android/SDL_android.cpp	Tue Mar 19 23:03:57 2013 -0700
     3.3 @@ -31,11 +31,15 @@
     3.4  
     3.5  extern "C" {
     3.6  #include "../../events/SDL_events_c.h"
     3.7 +#include "../../joystick/android/SDL_androidjoystick.h"
     3.8  #include "../../video/android/SDL_androidkeyboard.h"
     3.9  #include "../../video/android/SDL_androidtouch.h"
    3.10  #include "../../video/android/SDL_androidvideo.h"
    3.11  
    3.12  #include <android/log.h>
    3.13 +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    3.14 +#include <android/sensor.h>
    3.15 +#endif
    3.16  #include <pthread.h>
    3.17  #include <sys/types.h>
    3.18  #include <unistd.h>
    3.19 @@ -76,9 +80,11 @@
    3.20  static jmethodID midAudioWriteByteBuffer;
    3.21  static jmethodID midAudioQuit;
    3.22  
    3.23 +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    3.24  // Accelerometer data storage
    3.25  static float fLastAccelerometer[3];
    3.26  static bool bHasNewData;
    3.27 +#endif
    3.28  
    3.29  /*******************************************************************************
    3.30                   Functions called by JNI
    3.31 @@ -130,7 +136,9 @@
    3.32      midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
    3.33                                  "audioQuit", "()V");
    3.34  
    3.35 +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    3.36      bHasNewData = false;
    3.37 +#endif
    3.38  
    3.39      if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
    3.40         !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
    3.41 @@ -147,6 +155,27 @@
    3.42      Android_SetScreenResolution(width, height, format);
    3.43  }
    3.44  
    3.45 +// Paddown
    3.46 +extern "C" void Java_org_libsdl_app_SDLActivity_onNativePadDown(
    3.47 +                                    JNIEnv* env, jclass jcls, jint padId, jint keycode)
    3.48 +{
    3.49 +    Android_OnPadDown(padId, keycode);
    3.50 +}
    3.51 +
    3.52 +// Padup
    3.53 +extern "C" void Java_org_libsdl_app_SDLActivity_onNativePadUp(
    3.54 +                                    JNIEnv* env, jclass jcls, jint padId, jint keycode)
    3.55 +{
    3.56 +    Android_OnPadUp(padId, keycode);
    3.57 +}
    3.58 +
    3.59 +// Joysticks
    3.60 +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeJoy(
    3.61 +                                    JNIEnv* env, jclass jcls, jint joyId, jint axisNum, jfloat value)
    3.62 +{
    3.63 +    Android_OnJoy(joyId, axisNum, value);
    3.64 +}
    3.65 +
    3.66  // Keydown
    3.67  extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
    3.68                                      JNIEnv* env, jclass jcls, jint keycode)
    3.69 @@ -170,6 +199,7 @@
    3.70      Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
    3.71  }
    3.72  
    3.73 +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    3.74  // Accelerometer
    3.75  extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
    3.76                                      JNIEnv* env, jclass jcls,
    3.77 @@ -180,6 +210,7 @@
    3.78      fLastAccelerometer[2] = z;
    3.79      bHasNewData = true;
    3.80  }
    3.81 +#endif
    3.82  
    3.83  // Quit
    3.84  extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
    3.85 @@ -347,6 +378,7 @@
    3.86      }
    3.87  }
    3.88  
    3.89 +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    3.90  extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
    3.91  {
    3.92      int i;
    3.93 @@ -362,6 +394,7 @@
    3.94  
    3.95      return retval;
    3.96  }
    3.97 +#endif
    3.98  
    3.99  static void Android_JNI_ThreadDestroyed(void* value) {
   3.100      /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   3.101 @@ -1082,6 +1115,101 @@
   3.102      return 0;
   3.103  }
   3.104  
   3.105 +// Initialize the joystick subsystem on the Java side
   3.106 +int Android_JNI_JoystickInit()
   3.107 +{
   3.108 +    JNIEnv* env = Android_JNI_GetEnv();
   3.109 +    if (!env) {
   3.110 +        return -1;
   3.111 +    }
   3.112 +    jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickInit", "()V");
   3.113 +    if (!mid) {
   3.114 +        return -1;
   3.115 +    }
   3.116 +    env->CallStaticVoidMethod(mActivityClass, mid);
   3.117 +    return 0;
   3.118 +}
   3.119 +
   3.120 +// Quit the joystick subsystem on the Java side
   3.121 +int Android_JNI_JoystickQuit()
   3.122 +{
   3.123 +    JNIEnv* env = Android_JNI_GetEnv();
   3.124 +    if (!env) {
   3.125 +        return -1;
   3.126 +    }
   3.127 +    jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickQuit", "()V");
   3.128 +    if (!mid) {
   3.129 +        return -1;
   3.130 +    }
   3.131 +    env->CallStaticVoidMethod(mActivityClass, mid);
   3.132 +    return 0;
   3.133 +}
   3.134 +
   3.135 +// return the total number of plugged in joysticks
   3.136 +extern "C" int Android_JNI_GetNumJoysticks()
   3.137 +{
   3.138 +    JNIEnv* env = Android_JNI_GetEnv();
   3.139 +    if (!env) {
   3.140 +        return -1;
   3.141 +    }
   3.142 +    jmethodID mid = env->GetStaticMethodID(mActivityClass, "getNumJoysticks", "()I");
   3.143 +    if (!mid) {
   3.144 +        return -1;
   3.145 +    }
   3.146 +    
   3.147 +    return env->CallStaticIntMethod(mActivityClass, mid);
   3.148 +}
   3.149 +
   3.150 +// Return the name of joystick number "index"
   3.151 +extern "C" char* Android_JNI_GetJoystickName(int index)
   3.152 +{
   3.153 +    JNIEnv* env = Android_JNI_GetEnv();
   3.154 +    if (!env) {
   3.155 +        return SDL_strdup("");
   3.156 +    }
   3.157 +
   3.158 +    jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickName", "(I)Ljava/lang/String;");
   3.159 +    if (!mid) {
   3.160 +            return SDL_strdup("");
   3.161 +    }
   3.162 +    jstring string = reinterpret_cast<jstring>(env->CallStaticObjectMethod(mActivityClass, mid, index));
   3.163 +    const char* utf = env->GetStringUTFChars(string, 0);
   3.164 +    if (!utf) {
   3.165 +            return SDL_strdup("");
   3.166 +    }
   3.167 +
   3.168 +    char* text = SDL_strdup(utf);
   3.169 +    env->ReleaseStringUTFChars(string, utf);
   3.170 +    return text;
   3.171 +}
   3.172 +
   3.173 +// return the number of axes in the given joystick
   3.174 +extern "C" int Android_JNI_GetJoystickNumOfAxes(int index)
   3.175 +{
   3.176 +    JNIEnv* env = Android_JNI_GetEnv();
   3.177 +    if (!env) {
   3.178 +        return -1;
   3.179 +    }
   3.180 +    jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickNumOfAxes", "(I)I");
   3.181 +    if (!mid) {
   3.182 +        return -1;
   3.183 +    }
   3.184 +    
   3.185 +    return env->CallStaticIntMethod(mActivityClass, mid, index);
   3.186 +}
   3.187 +
   3.188 +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
   3.189 +// Return the name of the default accelerometer
   3.190 +// This is much easier to be done with NDK than with JNI
   3.191 +extern "C" char* Android_GetAccelName()
   3.192 +{
   3.193 +    ASensorManager* mSensorManager = ASensorManager_getInstance();
   3.194 +    ASensor const* mAccelerometer = ASensorManager_getDefaultSensor(mSensorManager, ASENSOR_TYPE_ACCELEROMETER);
   3.195 +
   3.196 +    return SDL_strdup(ASensor_getName(mAccelerometer));
   3.197 +}
   3.198 +#endif
   3.199 +
   3.200  // sends message to be handled on the UI event dispatch thread
   3.201  extern "C" int Android_JNI_SendMessage(int command, int param)
   3.202  {
     4.1 --- a/src/core/android/SDL_android.h	Tue Mar 19 22:25:02 2013 -0700
     4.2 +++ b/src/core/android/SDL_android.h	Tue Mar 19 23:03:57 2013 -0700
     4.3 @@ -33,7 +33,9 @@
     4.4  extern SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion, int red, int green, int blue, int alpha, int buffer, int depth, int stencil, int buffers, int samples);
     4.5  extern void Android_JNI_SwapWindow();
     4.6  extern void Android_JNI_SetActivityTitle(const char *title);
     4.7 +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
     4.8  extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]);
     4.9 +#endif
    4.10  extern void Android_JNI_ShowTextInput(SDL_Rect *inputRect);
    4.11  extern void Android_JNI_HideTextInput();
    4.12  
    4.13 @@ -60,6 +62,16 @@
    4.14  /* Power support */
    4.15  int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent);
    4.16  
    4.17 +/* Joystick/accelerometer support */
    4.18 +int Android_JNI_JoystickInit();
    4.19 +int Android_JNI_JoystickQuit();
    4.20 +int Android_JNI_GetNumJoysticks();
    4.21 +char* Android_JNI_GetJoystickName(int i);
    4.22 +int Android_JNI_GetJoystickNumOfAxes(int index);
    4.23 +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK
    4.24 +char* Android_GetAccelName();
    4.25 +#endif
    4.26 +
    4.27  // Threads
    4.28  #include <jni.h>
    4.29  static void Android_JNI_ThreadDestroyed(void*);