From e8c4f3302650e99625c535bcb8c46719726253ff Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 19 Mar 2013 23:03:57 -0700 Subject: [PATCH] Fixed bug 1700 - Joysticks not supported in Android --- android-project/project.properties | 2 +- .../src/org/libsdl/app/SDLActivity.java | 159 ++++++++++++++++-- src/core/android/SDL_android.cpp | 128 ++++++++++++++ src/core/android/SDL_android.h | 12 ++ 4 files changed, 286 insertions(+), 15 deletions(-) diff --git a/android-project/project.properties b/android-project/project.properties index b7c2081d5..0f507e530 100644 --- a/android-project/project.properties +++ b/android-project/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-10 +target=android-12 diff --git a/android-project/src/org/libsdl/app/SDLActivity.java b/android-project/src/org/libsdl/app/SDLActivity.java index e0a850f42..55ab5cb4b 100644 --- a/android-project/src/org/libsdl/app/SDLActivity.java +++ b/android-project/src/org/libsdl/app/SDLActivity.java @@ -24,6 +24,8 @@ import android.content.*; import java.lang.*; +import java.util.List; +import java.util.ArrayList; /** @@ -42,6 +44,11 @@ public class SDLActivity extends Activity { // This is what SDL runs in. It invokes SDL_main(), eventually private static Thread mSDLThread; + + // Joystick + private static List mJoyIdList; + // TODO: Have a (somewhat) more efficient way of storing these? + private static List> mJoyAxesLists; // Audio private static Thread mAudioThread; @@ -156,12 +163,15 @@ void sendCommand(int command, Object data) { public static native void nativePause(); public static native void nativeResume(); public static native void onNativeResize(int x, int y, int format); + public static native void onNativePadDown(int padId, int keycode); + public static native void onNativePadUp(int padId, int keycode); + public static native void onNativeJoy(int joyId, int axisNum, float value); public static native void onNativeKeyDown(int keycode); public static native void onNativeKeyUp(int keycode); public static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p); - public static native void onNativeAccel(float x, float y, float z); +// public static native void onNativeAccel(float x, float y, float z); public static native void nativeRunAudioThread(); @@ -180,6 +190,74 @@ public static void setActivityTitle(String title) { mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); } + // Call when initializing the joystick subsystem + public static void joystickInit() { + mJoyIdList = new ArrayList(); + mJoyAxesLists = new ArrayList>(); + + int[] deviceIds = InputDevice.getDeviceIds(); + for(int i=0; i axesList = new ArrayList(); + /* With API 12 and above we can get a list of all motion + * ranges, hence all axes. Some of them may be irrelevant + * (e.g. an embedded trackpad). We filter the desired axes. + */ + if(Build.VERSION.SDK_INT >= 12) { + List rangesList = InputDevice.getDevice(deviceIds[i]).getMotionRanges(); + for (InputDevice.MotionRange range : rangesList) { + // Skip any possibly unrelated axis + if ( (range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + axesList.add(range.getAxis()); + } + } + } else { + // In older versions, we can assume a sane X-Y default configuration + axesList.add(MotionEvent.AXIS_X); + axesList.add(MotionEvent.AXIS_Y); + } + mJoyAxesLists.add(axesList); + } + } + } + + // Call when one clears joystick subsystem resources + public static void joystickQuit() { + mJoyIdList = null; + mJoyAxesLists = null; + } + + public static int getNumJoysticks() { + if (mJoyIdList == null) + return -1; + return mJoyIdList.size(); + } + + public static String getJoystickName(int joy) { + if (mJoyIdList == null) + return null; + return InputDevice.getDevice(mJoyIdList.get(joy)).getName(); + } + + public static List getJoystickAxesList(int joy) { + if (mJoyIdList == null) + return null; + return mJoyAxesLists.get(joy); + } + + public static int getJoystickNumOfAxes(int joy) { + if (mJoyIdList == null) + return -1; + return mJoyAxesLists.get(joy).size(); + } + + public static int getJoyId(int devId) { + if (mJoyIdList == null) + return -1; + return mJoyIdList.indexOf(devId); + } + public static void sendMessage(int command, int param) { mSingleton.sendCommand(command, Integer.valueOf(param)); } @@ -478,7 +556,12 @@ public SDLSurface(Context context) { setFocusableInTouchMode(true); requestFocus(); setOnKeyListener(this); - setOnTouchListener(this); + setOnTouchListener(this); + + // Listen to joystick motion events if supported + if (Build.VERSION.SDK_INT >= 12) { + setOnGenericMotionListener(new SDLOnGenericMotionListener()); + } mSensorManager = (SensorManager)context.getSystemService("sensor"); @@ -568,18 +651,65 @@ public void onDraw(Canvas canvas) {} + // Listen to joystick motion events if supported (API >= 12) + private static class SDLOnGenericMotionListener implements View.OnGenericMotionListener { + @Override + public boolean onGenericMotion(View view, MotionEvent event) { + int actionPointerIndex = event.getActionIndex(); + int action = event.getActionMasked(); + + if ( (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + switch(action) { + case MotionEvent.ACTION_MOVE: + int id = SDLActivity.getJoyId( event.getDeviceId() ); + // The joystick subsystem may be uninitialized, so ignore + if (id < 0) + return true; + // Update values for all joystick axes + List axes = SDLActivity.getJoystickAxesList(id); + for (int axisIndex = 0; axisIndex < axes.size(); axisIndex++) { + SDLActivity.onNativeJoy(id, axisIndex, event.getAxisValue(axes.get(axisIndex), actionPointerIndex)); + } + + return true; + } + } + return false; + } + } + // Key events public boolean onKey(View v, int keyCode, KeyEvent event) { - - if (event.getAction() == KeyEvent.ACTION_DOWN) { - //Log.v("SDL", "key down: " + keyCode); - SDLActivity.onNativeKeyDown(keyCode); - return true; - } - else if (event.getAction() == KeyEvent.ACTION_UP) { - //Log.v("SDL", "key up: " + keyCode); - SDLActivity.onNativeKeyUp(keyCode); - return true; + /* Dispatch the different events depending on where they come from: + * If the input device has some joystick source (probably differing + * from the source to which the given key belongs), assume it is a + * game controller button. Otherwise, assume a keyboard key. + * This should also take care of some kinds of manually toggled soft + * keyboards (i.e. not via the SDL text input API). + */ + if ( (event.getDevice().getSources() & 0x00000010 /* API 12: InputDevice.SOURCE_CLASS_JOYSTICK*/) != 0) { + int id = SDLActivity.getJoyId( event.getDeviceId() ); + // The joystick subsystem may be uninitialized, so ignore + if (id < 0) + return true; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + SDLActivity.onNativePadDown(id, keyCode); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + SDLActivity.onNativePadUp(id, keyCode); + return true; + } + } else { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + //Log.v("SDL", "key down: " + keyCode); + SDLActivity.onNativeKeyDown(keyCode); + return true; + } + else if (event.getAction() == KeyEvent.ACTION_UP) { + //Log.v("SDL", "key up: " + keyCode); + SDLActivity.onNativeKeyUp(keyCode); + return true; + } } return false; @@ -614,7 +744,7 @@ public boolean onTouch(View v, MotionEvent event) { } } return true; - } + } // Sensor events public void enableSensor(int sensortype, boolean enabled) { @@ -634,13 +764,14 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onSensorChanged(SensorEvent event) { +/* if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { SDLActivity.onNativeAccel(event.values[0] / SensorManager.GRAVITY_EARTH, event.values[1] / SensorManager.GRAVITY_EARTH, event.values[2] / SensorManager.GRAVITY_EARTH); } +*/ } - } /* This is a fake invisible editor view that receives the input and defines the diff --git a/src/core/android/SDL_android.cpp b/src/core/android/SDL_android.cpp index 02e28ff9a..d87e724c2 100644 --- a/src/core/android/SDL_android.cpp +++ b/src/core/android/SDL_android.cpp @@ -31,11 +31,15 @@ extern "C" { #include "../../events/SDL_events_c.h" +#include "../../joystick/android/SDL_androidjoystick.h" #include "../../video/android/SDL_androidkeyboard.h" #include "../../video/android/SDL_androidtouch.h" #include "../../video/android/SDL_androidvideo.h" #include +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK +#include +#endif #include #include #include @@ -76,9 +80,11 @@ static jmethodID midAudioWriteShortBuffer; static jmethodID midAudioWriteByteBuffer; static jmethodID midAudioQuit; +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK // Accelerometer data storage static float fLastAccelerometer[3]; static bool bHasNewData; +#endif /******************************************************************************* Functions called by JNI @@ -130,7 +136,9 @@ extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls) midAudioQuit = mEnv->GetStaticMethodID(mActivityClass, "audioQuit", "()V"); +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK bHasNewData = false; +#endif if(!midCreateGLContext || !midFlipBuffers || !midAudioInit || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) { @@ -147,6 +155,27 @@ extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize( Android_SetScreenResolution(width, height, format); } +// Paddown +extern "C" void Java_org_libsdl_app_SDLActivity_onNativePadDown( + JNIEnv* env, jclass jcls, jint padId, jint keycode) +{ + Android_OnPadDown(padId, keycode); +} + +// Padup +extern "C" void Java_org_libsdl_app_SDLActivity_onNativePadUp( + JNIEnv* env, jclass jcls, jint padId, jint keycode) +{ + Android_OnPadUp(padId, keycode); +} + +// Joysticks +extern "C" void Java_org_libsdl_app_SDLActivity_onNativeJoy( + JNIEnv* env, jclass jcls, jint joyId, jint axisNum, jfloat value) +{ + Android_OnJoy(joyId, axisNum, value); +} + // Keydown extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown( JNIEnv* env, jclass jcls, jint keycode) @@ -170,6 +199,7 @@ extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch( Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p); } +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK // Accelerometer extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel( JNIEnv* env, jclass jcls, @@ -180,6 +210,7 @@ extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel( fLastAccelerometer[2] = z; bHasNewData = true; } +#endif // Quit extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit( @@ -347,6 +378,7 @@ extern "C" void Android_JNI_SetActivityTitle(const char *title) } } +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3]) { int i; @@ -362,6 +394,7 @@ extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3]) return retval; } +#endif static void Android_JNI_ThreadDestroyed(void* value) { /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ @@ -1082,6 +1115,101 @@ extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery return 0; } +// Initialize the joystick subsystem on the Java side +int Android_JNI_JoystickInit() +{ + JNIEnv* env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickInit", "()V"); + if (!mid) { + return -1; + } + env->CallStaticVoidMethod(mActivityClass, mid); + return 0; +} + +// Quit the joystick subsystem on the Java side +int Android_JNI_JoystickQuit() +{ + JNIEnv* env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + jmethodID mid = env->GetStaticMethodID(mActivityClass, "joystickQuit", "()V"); + if (!mid) { + return -1; + } + env->CallStaticVoidMethod(mActivityClass, mid); + return 0; +} + +// return the total number of plugged in joysticks +extern "C" int Android_JNI_GetNumJoysticks() +{ + JNIEnv* env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + jmethodID mid = env->GetStaticMethodID(mActivityClass, "getNumJoysticks", "()I"); + if (!mid) { + return -1; + } + + return env->CallStaticIntMethod(mActivityClass, mid); +} + +// Return the name of joystick number "index" +extern "C" char* Android_JNI_GetJoystickName(int index) +{ + JNIEnv* env = Android_JNI_GetEnv(); + if (!env) { + return SDL_strdup(""); + } + + jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickName", "(I)Ljava/lang/String;"); + if (!mid) { + return SDL_strdup(""); + } + jstring string = reinterpret_cast(env->CallStaticObjectMethod(mActivityClass, mid, index)); + const char* utf = env->GetStringUTFChars(string, 0); + if (!utf) { + return SDL_strdup(""); + } + + char* text = SDL_strdup(utf); + env->ReleaseStringUTFChars(string, utf); + return text; +} + +// return the number of axes in the given joystick +extern "C" int Android_JNI_GetJoystickNumOfAxes(int index) +{ + JNIEnv* env = Android_JNI_GetEnv(); + if (!env) { + return -1; + } + jmethodID mid = env->GetStaticMethodID(mActivityClass, "getJoystickNumOfAxes", "(I)I"); + if (!mid) { + return -1; + } + + return env->CallStaticIntMethod(mActivityClass, mid, index); +} + +#if ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK +// Return the name of the default accelerometer +// This is much easier to be done with NDK than with JNI +extern "C" char* Android_GetAccelName() +{ + ASensorManager* mSensorManager = ASensorManager_getInstance(); + ASensor const* mAccelerometer = ASensorManager_getDefaultSensor(mSensorManager, ASENSOR_TYPE_ACCELEROMETER); + + return SDL_strdup(ASensor_getName(mAccelerometer)); +} +#endif + // sends message to be handled on the UI event dispatch thread extern "C" int Android_JNI_SendMessage(int command, int param) { diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h index 7eddcfac8..f57728461 100644 --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -33,7 +33,9 @@ extern "C" { 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); extern void Android_JNI_SwapWindow(); extern void Android_JNI_SetActivityTitle(const char *title); +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK extern SDL_bool Android_JNI_GetAccelerometerValues(float values[3]); +#endif extern void Android_JNI_ShowTextInput(SDL_Rect *inputRect); extern void Android_JNI_HideTextInput(); @@ -60,6 +62,16 @@ SDL_bool Android_JNI_HasClipboardText(); /* Power support */ int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent); +/* Joystick/accelerometer support */ +int Android_JNI_JoystickInit(); +int Android_JNI_JoystickQuit(); +int Android_JNI_GetNumJoysticks(); +char* Android_JNI_GetJoystickName(int i); +int Android_JNI_GetJoystickNumOfAxes(int index); +#ifdef ENABLE_ACCELOMETER_AS_EMULATED_JOYSTICK +char* Android_GetAccelName(); +#endif + // Threads #include static void Android_JNI_ThreadDestroyed(void*);