Browse Source

Fixed bug 3191 - haptic system on android?

Patch provided by jintiao and Milan Nikolic, thanks!
Sam Lantinga 7 years ago
parent
commit
0a52db54bd

+ 1 - 1
Android.mk

@@ -28,7 +28,7 @@ LOCAL_SRC_FILES := \
 	$(wildcard $(LOCAL_PATH)/src/events/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/file/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/haptic/*.c) \
-	$(wildcard $(LOCAL_PATH)/src/haptic/dummy/*.c) \
+	$(wildcard $(LOCAL_PATH)/src/haptic/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/joystick/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/joystick/android/*.c) \
 	$(wildcard $(LOCAL_PATH)/src/loadso/dlopen/*.c) \

+ 2 - 0
android-project/AndroidManifest.xml

@@ -16,6 +16,8 @@
 
     <!-- Allow writing to external storage -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <!-- Allow access to the vibrator -->
+    <uses-permission android:name="android.permission.VIBRATE" />
 
     <!-- if you want to capture audio, uncomment this. -->
     <!-- <uses-permission android:name="android.permission.RECORD_AUDIO" /> -->

+ 96 - 0
android-project/src/org/libsdl/app/SDLActivity.java

@@ -61,6 +61,7 @@ public class SDLActivity extends Activity {
     protected static View mTextEdit;
     protected static ViewGroup mLayout;
     protected static SDLJoystickHandler mJoystickHandler;
+    protected static SDLHapticHandler mHapticHandler;
 
     // This is what SDL runs in. It invokes SDL_main(), eventually
     protected static Thread mSDLThread;
@@ -113,6 +114,7 @@ public class SDLActivity extends Activity {
         mTextEdit = null;
         mLayout = null;
         mJoystickHandler = null;
+        mHapticHandler = null;
         mSDLThread = null;
         mAudioTrack = null;
         mAudioRecord = null;
@@ -182,6 +184,7 @@ public class SDLActivity extends Activity {
         else {
             mJoystickHandler = new SDLJoystickHandler();
         }
+        mHapticHandler = new SDLHapticHandler();
 
         mLayout = new RelativeLayout(this);
         mLayout.addView(mSurface);
@@ -498,6 +501,8 @@ public class SDLActivity extends Activity {
                                                int is_accelerometer, int nbuttons,
                                                int naxes, int nhats, int nballs);
     public static native int nativeRemoveJoystick(int device_id);
+    public static native int nativeAddHaptic(int device_id, String name);
+    public static native int nativeRemoveHaptic(int device_id);
     public static native String nativeGetHint(String name);
 
     /**
@@ -1704,6 +1709,18 @@ class SDLJoystickHandler_API12 extends SDLJoystickHandler {
         return null;
     }
 
+    public static void pollHapticDevices() {
+        if (SDLActivity.mSDLThread != null) {
+            mHapticHandler.pollHapticDevices();
+        }
+    }
+
+    public static void hapticRun(int device_id, int length) {
+        if (SDLActivity.mSDLThread != null) {
+            mHapticHandler.run(device_id, length);
+        }
+    }
+
     @Override
     public boolean handleMotionEvent(MotionEvent event) {
         if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
@@ -1779,3 +1796,82 @@ class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
         return false;
     }
 }
+
+class SDLHapticHandler {
+
+    class SDLHaptic {
+        public int device_id;
+        public String name;
+        public Vibrator vib;
+    }
+
+    private ArrayList<SDLHaptic> mHaptics;
+    
+    public SDLHapticHandler() {
+        mHaptics = new ArrayList<SDLHaptic>();
+    }
+
+    public void run(int device_id, int length) {
+        SDLHaptic haptic = getHaptic(device_id);
+        if (haptic != null) {
+            haptic.vib.vibrate (length);
+        }
+    }
+
+    public void pollHapticDevices() {
+        int[] deviceIds = InputDevice.getDeviceIds();
+        // It helps processing the device ids in reverse order
+        // For example, in the case of the XBox 360 wireless dongle,
+        // so the first controller seen by SDL matches what the receiver
+        // considers to be the first controller
+
+        for(int i=deviceIds.length-1; i>-1; i--) {
+            SDLHaptic haptic = getHaptic(deviceIds[i]);
+            if (haptic == null) {
+                InputDevice device = InputDevice.getDevice(deviceIds[i]);
+                Vibrator vib = device.getVibrator ();
+                if(vib.hasVibrator ()) {
+                    haptic = new SDLHaptic();
+                    haptic.device_id = deviceIds[i];
+                    haptic.name = device.getName();
+                    haptic.vib = vib;
+                    mHaptics.add(haptic);
+                    SDLActivity.nativeAddHaptic(haptic.device_id, haptic.name);
+                }
+            }
+        }
+
+        /* Check removed devices */
+        ArrayList<Integer> removedDevices = new ArrayList<Integer>();
+        for(int i=0; i < mHaptics.size(); i++) {
+            int device_id = mHaptics.get(i).device_id;
+            int j;
+            for (j=0; j < deviceIds.length; j++) {
+                if (device_id == deviceIds[j]) break;
+            }
+            if (j == deviceIds.length) {
+                removedDevices.add(device_id);
+            }
+        }
+
+        for(int i=0; i < removedDevices.size(); i++) {
+            int device_id = removedDevices.get(i);
+            SDLActivity.nativeRemoveHaptic(device_id);
+            for (int j=0; j < mHaptics.size(); j++) {
+                if (mHaptics.get(j).device_id == device_id) {
+                    mHaptics.remove(j);
+                    break;
+                }
+            }
+        }
+    }
+
+    protected SDLHaptic getHaptic(int device_id) {
+        for(int i=0; i < mHaptics.size(); i++) {
+            if (mHaptics.get(i).device_id == device_id) {
+                return mHaptics.get(i);
+            }
+        }
+        return null;
+    }   
+}

+ 1 - 1
include/SDL_config_android.h

@@ -118,7 +118,7 @@
 
 /* Enable various input drivers */
 #define SDL_JOYSTICK_ANDROID    1
-#define SDL_HAPTIC_DUMMY    1
+#define SDL_HAPTIC_ANDROID    1
 
 /* Enable various shared object loading systems */
 #define SDL_LOADSO_DLOPEN   1

+ 40 - 1
src/core/android/SDL_android.c

@@ -37,6 +37,7 @@
 #include "../../video/android/SDL_androidvideo.h"
 #include "../../video/android/SDL_androidwindow.h"
 #include "../../joystick/android/SDL_sysjoystick_c.h"
+#include "../../haptic/android/SDL_syshaptic_c.h"
 
 #include <android/log.h>
 #include <pthread.h>
@@ -177,6 +178,8 @@ static jmethodID midCaptureReadShortBuffer;
 static jmethodID midCaptureReadByteBuffer;
 static jmethodID midCaptureClose;
 static jmethodID midPollInputDevices;
+static jmethodID midPollHapticDevices;
+static jmethodID midHapticRun;
 
 /* Accelerometer data storage */
 static float fLastAccelerometer[3];
@@ -237,13 +240,17 @@ JNIEXPORT void JNICALL SDL_Android_Init(JNIEnv* mEnv, jclass cls)
                                 "captureClose", "()V");
     midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
                                 "pollInputDevices", "()V");
+    midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+                                "pollHapticDevices", "()V");
+    midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+                                "hapticRun", "(II)V");
 
     bHasNewData = SDL_FALSE;
 
     if (!midGetNativeSurface ||
        !midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose ||
        !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose ||
-       !midPollInputDevices) {
+       !midPollInputDevices || !midPollHapticDevices || !midHapticRun) {
         __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!");
@@ -323,6 +330,25 @@ JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeRemoveJoystick)(
     return Android_RemoveJoystick(device_id);
 }
 
+JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeAddHaptic(
+    JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
+{
+    int retval;
+    const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
+
+    retval = Android_AddHaptic(device_id, name);
+
+    (*env)->ReleaseStringUTFChars(env, device_name, name);
+
+    return retval;
+}
+
+JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeRemoveHaptic(
+    JNIEnv* env, jclass jcls, jint device_id)
+{
+    return Android_RemoveHaptic(device_id);
+}
+
 
 /* Surface Created */
 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
@@ -1570,6 +1596,19 @@ void Android_JNI_PollInputDevices(void)
     (*env)->CallStaticVoidMethod(env, mActivityClass, midPollInputDevices);    
 }
 
+void Android_JNI_PollHapticDevices(void)
+{
+    JNIEnv *env = Android_JNI_GetEnv();
+    (*env)->CallStaticVoidMethod(env, mActivityClass, midPollHapticDevices);
+}
+    
+void Android_JNI_HapticRun(int device_id, int length)
+{
+    JNIEnv *env = Android_JNI_GetEnv();
+    (*env)->CallStaticVoidMethod(env, mActivityClass, midHapticRun, device_id, length);
+}
+
+
 /* See SDLActivity.java for constants. */
 #define COMMAND_SET_KEEP_SCREEN_ON    5
 

+ 4 - 0
src/core/android/SDL_android.h

@@ -67,6 +67,10 @@ int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seco
 /* Joystick support */
 void Android_JNI_PollInputDevices(void);
 
+/* Haptic support */
+void Android_JNI_PollHapticDevices(void);
+void Android_JNI_HapticRun(int device_id, int length);
+
 /* Video */
 void Android_JNI_SuspendScreenSaver(SDL_bool suspend);
 

+ 1 - 0
src/haptic/SDL_haptic.c

@@ -313,6 +313,7 @@ SDL_HapticOpenFromJoystick(SDL_Joystick * joystick)
     SDL_memset(haptic, 0, sizeof(SDL_Haptic));
     haptic->rumble_id = -1;
     if (SDL_SYS_HapticOpenFromJoystick(haptic, joystick) < 0) {
+        SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
         SDL_free(haptic);
         return NULL;
     }

+ 358 - 0
src/haptic/android/SDL_syshaptic.c

@@ -0,0 +1,358 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2014 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_internal.h"
+
+#ifdef SDL_HAPTIC_ANDROID
+
+#include "SDL_assert.h"
+#include "SDL_timer.h"
+#include "SDL_syshaptic_c.h"
+#include "../SDL_syshaptic.h"
+#include "SDL_haptic.h"
+#include "../../core/android/SDL_android.h"
+#include "SDL_joystick.h"
+#include "../../joystick/SDL_sysjoystick.h"     /* For the real SDL_Joystick */
+#include "../../joystick/android/SDL_sysjoystick_c.h"     /* For joystick hwdata */
+
+
+typedef struct SDL_hapticlist_item
+{
+    int device_id;
+    char *name;
+    SDL_Haptic *haptic;
+    struct SDL_hapticlist_item *next;
+} SDL_hapticlist_item;
+
+static SDL_hapticlist_item *SDL_hapticlist = NULL;
+static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
+static int numhaptics = 0;
+
+
+int
+SDL_SYS_HapticInit(void)
+{
+    /* Support for device connect/disconnect is API >= 16 only,
+     * so we poll every three seconds
+     * Ref: http://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
+     */
+    static Uint32 timeout = 0;
+    if (SDL_TICKS_PASSED(SDL_GetTicks(), timeout)) {
+        timeout = SDL_GetTicks() + 3000;
+        Android_JNI_PollHapticDevices();
+    }
+    return (numhaptics);
+}
+
+int
+SDL_SYS_NumHaptics(void)
+{
+    return (numhaptics);
+}
+
+static SDL_hapticlist_item *
+HapticByOrder(int index)
+{
+    SDL_hapticlist_item *item = SDL_hapticlist;
+    if ((index < 0) || (index >= numhaptics)) {
+        return NULL;
+    }
+    while (index > 0) {
+        SDL_assert(item != NULL);
+        --index;
+        item = item->next;
+    }
+    return item;
+}
+
+static SDL_hapticlist_item *
+HapticByDevId (int device_id)
+{
+    SDL_hapticlist_item *item;
+    for (item = SDL_hapticlist; item != NULL; item = item->next) {
+        if (device_id == item->device_id) {
+            SDL_Log("=+=+=+=+=+= HapticByDevId id [%d]", device_id);
+            return item;
+        }
+    }
+    return NULL;
+}
+
+const char *
+SDL_SYS_HapticName(int index)
+{
+    SDL_hapticlist_item *item = HapticByOrder(index);
+    if (item == NULL ) {
+        SDL_SetError("No such device");
+        return NULL;
+    }
+    return item->name;
+}
+
+
+static SDL_hapticlist_item *
+OpenHaptic(SDL_Haptic *haptic, SDL_hapticlist_item *item)
+{
+    if (item == NULL ) {
+        SDL_SetError("No such device");
+        return NULL;
+    }
+    if (item->haptic != NULL) {
+        SDL_SetError("Haptic already opened");
+        return NULL;
+    }
+
+    haptic->hwdata = (struct haptic_hwdata *)item;
+    item->haptic = haptic;
+
+    haptic->supported = SDL_HAPTIC_LEFTRIGHT;
+    haptic->neffects = 1;
+    haptic->nplaying = haptic->neffects;
+    haptic->effects = (struct haptic_effect *)SDL_malloc (sizeof (struct haptic_effect) * haptic->neffects);
+    if (haptic->effects == NULL) {
+        SDL_OutOfMemory();
+        return NULL;
+    }
+    SDL_memset(haptic->effects, 0, sizeof (struct haptic_effect) * haptic->neffects);
+    return item;
+}
+
+static SDL_hapticlist_item *
+OpenHapticByOrder(SDL_Haptic *haptic, int index)
+{
+    return OpenHaptic (haptic, HapticByOrder(index));
+}
+
+static SDL_hapticlist_item *
+OpenHapticByDevId(SDL_Haptic *haptic, int device_id)
+{
+    return OpenHaptic (haptic, HapticByDevId(device_id));
+}
+
+int
+SDL_SYS_HapticOpen(SDL_Haptic *haptic)
+{
+    return (OpenHapticByOrder(haptic, haptic->index) == NULL ? -1 : 0);
+}
+
+
+int
+SDL_SYS_HapticMouse(void)
+{
+    return 0;
+}
+
+
+int
+SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
+{
+    SDL_hapticlist_item *item;
+    item = HapticByDevId(((joystick_hwdata *)joystick->hwdata)->device_id);
+    int ret = (item != NULL ? 1 : 0);
+    return ret;
+}
+
+
+int
+SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
+{
+    return (OpenHapticByDevId(haptic, ((joystick_hwdata *)joystick->hwdata)->device_id) == NULL ? -1 : 0);
+}
+
+
+int
+SDL_SYS_JoystickSameHaptic(SDL_Haptic * haptic, SDL_Joystick * joystick)
+{
+    return (((SDL_hapticlist_item *)haptic->hwdata)->device_id == ((joystick_hwdata *)joystick->hwdata)->device_id ? 1 : 0);
+}
+
+
+void
+SDL_SYS_HapticClose(SDL_Haptic * haptic)
+{
+    ((SDL_hapticlist_item *)haptic->hwdata)->haptic = NULL;
+    haptic->hwdata = NULL;
+    return;
+}
+
+
+void
+SDL_SYS_HapticQuit(void)
+{
+    SDL_hapticlist_item *item = NULL;
+    SDL_hapticlist_item *next = NULL;
+
+    for (item = SDL_hapticlist; item; item = next) {
+        next = item->next;
+        SDL_free(item);
+    }
+
+    SDL_hapticlist = SDL_hapticlist_tail = NULL;
+    numhaptics = 0;
+    return;
+}
+
+
+int
+SDL_SYS_HapticNewEffect(SDL_Haptic * haptic,
+                        struct haptic_effect *effect, SDL_HapticEffect * base)
+{
+    return 0;
+}
+
+
+int
+SDL_SYS_HapticUpdateEffect(SDL_Haptic * haptic,
+                           struct haptic_effect *effect,
+                           SDL_HapticEffect * data)
+{
+    return 0;
+}
+
+
+int
+SDL_SYS_HapticRunEffect(SDL_Haptic * haptic, struct haptic_effect *effect,
+                        Uint32 iterations)
+{
+    Android_JNI_HapticRun (((SDL_hapticlist_item *)haptic->hwdata)->device_id, effect->effect.leftright.length);
+    return 0;
+}
+
+
+int
+SDL_SYS_HapticStopEffect(SDL_Haptic * haptic, struct haptic_effect *effect)
+{
+    return 0;
+}
+
+
+void
+SDL_SYS_HapticDestroyEffect(SDL_Haptic * haptic, struct haptic_effect *effect)
+{
+    return;
+}
+
+
+int
+SDL_SYS_HapticGetEffectStatus(SDL_Haptic * haptic,
+                              struct haptic_effect *effect)
+{
+    return 0;
+}
+
+
+int
+SDL_SYS_HapticSetGain(SDL_Haptic * haptic, int gain)
+{
+    return 0;
+}
+
+
+int
+SDL_SYS_HapticSetAutocenter(SDL_Haptic * haptic, int autocenter)
+{
+    return 0;
+}
+
+int
+SDL_SYS_HapticPause(SDL_Haptic * haptic)
+{
+    return 0;
+}
+
+int
+SDL_SYS_HapticUnpause(SDL_Haptic * haptic)
+{
+    return 0;
+}
+
+int
+SDL_SYS_HapticStopAll(SDL_Haptic * haptic)
+{
+    return 0;
+}
+
+
+
+int
+Android_AddHaptic(int device_id, const char *name)
+{
+    SDL_hapticlist_item *item;
+    item = (SDL_hapticlist_item *) SDL_calloc(1, sizeof (SDL_hapticlist_item));
+    if (item == NULL) {
+        return -1;
+    }
+
+    item->device_id = device_id;
+    item->name = SDL_strdup (name);
+    if (item->name == NULL) {
+        SDL_free (item);
+        return -1;
+    }
+
+    if (SDL_hapticlist_tail == NULL) {
+        SDL_hapticlist = SDL_hapticlist_tail = item;
+    } else {
+        SDL_hapticlist_tail->next = item;
+        SDL_hapticlist_tail = item;
+    }
+
+    ++numhaptics;
+    return numhaptics;
+}
+
+int 
+Android_RemoveHaptic(int device_id)
+{
+    SDL_hapticlist_item *item;
+    SDL_hapticlist_item *prev = NULL;
+
+    for (item = SDL_hapticlist; item != NULL; item = item->next) {
+        /* found it, remove it. */
+        if (device_id == item->device_id) {
+            const int retval = item->haptic ? item->haptic->index : -1;
+
+            if (prev != NULL) {
+                prev->next = item->next;
+            } else {
+                SDL_assert(SDL_hapticlist == item);
+                SDL_hapticlist = item->next;
+            }
+            if (item == SDL_hapticlist_tail) {
+                SDL_hapticlist_tail = prev;
+            }
+
+            /* Need to decrement the haptic count */
+            --numhaptics;
+            /* !!! TODO: Send a haptic remove event? */
+
+            SDL_free(item->name);
+            SDL_free(item);
+            return retval;
+        }
+        prev = item;
+    }
+    return -1;
+}
+
+
+#endif /* SDL_HAPTIC_ANDROID */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 12 - 0
src/haptic/android/SDL_syshaptic_c.h

@@ -0,0 +1,12 @@
+#include "SDL_config.h"
+
+#ifdef SDL_HAPTIC_ANDROID
+
+
+extern int Android_AddHaptic(int device_id, const char *name);
+extern int Android_RemoveHaptic(int device_id);
+
+
+#endif /* SDL_HAPTIC_ANDROID */
+
+/* vi: set ts=4 sw=4 expandtab: */

+ 6 - 0
src/joystick/android/SDL_sysjoystick_c.h

@@ -22,6 +22,10 @@
 #include "../../SDL_internal.h"
 
 #ifdef SDL_JOYSTICK_ANDROID
+
+#ifndef _SDL_sysjoystick_c_h
+#define _SDL_sysjoystick_c_h
+
 #include "../SDL_sysjoystick.h"
 
 extern int Android_OnPadDown(int device_id, int keycode);
@@ -47,6 +51,8 @@ typedef struct SDL_joylist_item
 
 typedef SDL_joylist_item joystick_hwdata;
 
+#endif /* _SDL_sysjoystick_c_h */
+
 #endif /* SDL_JOYSTICK_ANDROID */
 
 /* vi: set ts=4 sw=4 expandtab: */