Fixed bug 2266 - please add notifications for clipboard updates on Android
authorSam Lantinga <slouken@libsdl.org>
Sun, 27 Aug 2017 18:43:52 -0700
changeset 113556185fb86f046
parent 11354 323fc605104f
child 11356 0347c3e08f85
Fixed bug 2266 - please add notifications for clipboard updates on Android

Sylvain

Hi! here's a patch for that with two class loaded regarding API level.
Test both case : before API 11 and after.

I also remove now unused GetSystemServiceFromUIThread() and minor clean-up (haptic warning prototype).
android-project/src/org/libsdl/app/SDLActivity.java
src/core/android/SDL_android.c
     1.1 --- a/android-project/src/org/libsdl/app/SDLActivity.java	Sun Aug 27 18:36:54 2017 -0700
     1.2 +++ b/android-project/src/org/libsdl/app/SDLActivity.java	Sun Aug 27 18:43:52 2017 -0700
     1.3 @@ -63,6 +63,8 @@
     1.4      protected static ViewGroup mLayout;
     1.5      protected static SDLJoystickHandler mJoystickHandler;
     1.6      protected static SDLHapticHandler mHapticHandler;
     1.7 +    protected static SDLClipboardHandler mClipboardHandler;
     1.8 +
     1.9  
    1.10      // This is what SDL runs in. It invokes SDL_main(), eventually
    1.11      protected static Thread mSDLThread;
    1.12 @@ -116,6 +118,7 @@
    1.13          mLayout = null;
    1.14          mJoystickHandler = null;
    1.15          mHapticHandler = null;
    1.16 +        mClipboardHandler = null;
    1.17          mSDLThread = null;
    1.18          mAudioTrack = null;
    1.19          mAudioRecord = null;
    1.20 @@ -187,6 +190,13 @@
    1.21          }
    1.22          mHapticHandler = new SDLHapticHandler();
    1.23  
    1.24 +        if (Build.VERSION.SDK_INT >= 11) {
    1.25 +            mClipboardHandler = new SDLClipboardHandler_API11();
    1.26 +        } else {
    1.27 +            /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
    1.28 +            mClipboardHandler = new SDLClipboardHandler_Old();
    1.29 +        }
    1.30 +
    1.31          mLayout = new RelativeLayout(this);
    1.32          mLayout.addView(mSurface);
    1.33  
    1.34 @@ -498,6 +508,7 @@
    1.35                                              int action, float x,
    1.36                                              float y, float p);
    1.37      public static native void onNativeAccel(float x, float y, float z);
    1.38 +    public static native void onNativeClipboardChanged();
    1.39      public static native void onNativeSurfaceChanged();
    1.40      public static native void onNativeSurfaceDestroyed();
    1.41      public static native int nativeAddJoystick(int device_id, String name,
    1.42 @@ -607,35 +618,6 @@
    1.43          return mSingleton;
    1.44      }
    1.45  
    1.46 -    /**
    1.47 -     * This method is called by SDL using JNI.
    1.48 -     * @return result of getSystemService(name) but executed on UI thread.
    1.49 -     */
    1.50 -    public Object getSystemServiceFromUiThread(final String name) {
    1.51 -        final Object lock = new Object();
    1.52 -        final Object[] results = new Object[2]; // array for writable variables
    1.53 -        synchronized (lock) {
    1.54 -            runOnUiThread(new Runnable() {
    1.55 -                @Override
    1.56 -                public void run() {
    1.57 -                    synchronized (lock) {
    1.58 -                        results[0] = getSystemService(name);
    1.59 -                        results[1] = Boolean.TRUE;
    1.60 -                        lock.notify();
    1.61 -                    }
    1.62 -                }
    1.63 -            });
    1.64 -            if (results[1] == null) {
    1.65 -                try {
    1.66 -                    lock.wait();
    1.67 -                } catch (InterruptedException ex) {
    1.68 -                    ex.printStackTrace();
    1.69 -                }
    1.70 -            }
    1.71 -        }
    1.72 -        return results[0];
    1.73 -    }
    1.74 -
    1.75      static class ShowTextInputTask implements Runnable {
    1.76          /*
    1.77           * This is used to regulate the pan&scan method to have some offset from
    1.78 @@ -1182,6 +1164,29 @@
    1.79  
    1.80          return dialog;
    1.81      }
    1.82 +
    1.83 +    /**
    1.84 +     * This method is called by SDL using JNI.
    1.85 +     */
    1.86 +    public static boolean clipboardHasText() {
    1.87 +        return mClipboardHandler.clipboardHasText();
    1.88 +    }
    1.89 +    
    1.90 +    /**
    1.91 +     * This method is called by SDL using JNI.
    1.92 +     */
    1.93 +    public static String clipboardGetText() {
    1.94 +        return mClipboardHandler.clipboardGetText();
    1.95 +    }
    1.96 +
    1.97 +    /**
    1.98 +     * This method is called by SDL using JNI.
    1.99 +     */
   1.100 +    public static void clipboardSetText(String string) {
   1.101 +        mClipboardHandler.clipboardSetText(string);
   1.102 +        return;
   1.103 +    }
   1.104 +
   1.105  }
   1.106  
   1.107  /**
   1.108 @@ -1993,3 +1998,86 @@
   1.109          return null;
   1.110      }   
   1.111  }
   1.112 +
   1.113 +
   1.114 +interface SDLClipboardHandler {
   1.115 +
   1.116 +    public boolean clipboardHasText();
   1.117 +    public String clipboardGetText();
   1.118 +    public void clipboardSetText(String string);
   1.119 +
   1.120 +}
   1.121 +
   1.122 +
   1.123 +class SDLClipboardHandler_API11 implements
   1.124 +    SDLClipboardHandler, 
   1.125 +    android.content.ClipboardManager.OnPrimaryClipChangedListener {
   1.126 +
   1.127 +    protected android.content.ClipboardManager mClipMgr;
   1.128 +
   1.129 +    SDLClipboardHandler_API11() {
   1.130 +       mClipMgr = (android.content.ClipboardManager) SDLActivity.mSingleton.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
   1.131 +       mClipMgr.addPrimaryClipChangedListener(this);
   1.132 +    }
   1.133 +
   1.134 +    @Override
   1.135 +    public boolean clipboardHasText() {
   1.136 +       return mClipMgr.hasText();
   1.137 +    }
   1.138 +
   1.139 +    @Override
   1.140 +    public String clipboardGetText() {
   1.141 +        CharSequence text;
   1.142 +        text = mClipMgr.getText();
   1.143 +        if (text != null) {
   1.144 +           return text.toString();
   1.145 +        }
   1.146 +        return null;
   1.147 +    }
   1.148 +
   1.149 +    @Override
   1.150 +    public void clipboardSetText(String string) {
   1.151 +       mClipMgr.removePrimaryClipChangedListener(this);
   1.152 +       mClipMgr.setText(string);
   1.153 +       mClipMgr.addPrimaryClipChangedListener(this);
   1.154 +    }
   1.155 +    
   1.156 +    @Override
   1.157 +    public void onPrimaryClipChanged() {
   1.158 +        SDLActivity.onNativeClipboardChanged();
   1.159 +    }
   1.160 +
   1.161 +}
   1.162 +
   1.163 +class SDLClipboardHandler_Old implements
   1.164 +    SDLClipboardHandler {
   1.165 +   
   1.166 +    protected android.text.ClipboardManager mClipMgrOld;
   1.167 +  
   1.168 +    SDLClipboardHandler_Old() {
   1.169 +       mClipMgrOld = (android.text.ClipboardManager) SDLActivity.mSingleton.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
   1.170 +    }
   1.171 +
   1.172 +    @Override
   1.173 +    public boolean clipboardHasText() {
   1.174 +       return mClipMgrOld.hasText();
   1.175 +    }
   1.176 +
   1.177 +    @Override
   1.178 +    public String clipboardGetText() {
   1.179 +       CharSequence text;
   1.180 +       text = mClipMgrOld.getText();
   1.181 +       if (text != null) {
   1.182 +          return text.toString();
   1.183 +       }
   1.184 +       return null;
   1.185 +    }
   1.186 +
   1.187 +    @Override
   1.188 +    public void clipboardSetText(String string) {
   1.189 +       mClipMgrOld.setText(string);
   1.190 +       return;
   1.191 +    }
   1.192 +}
   1.193 +
   1.194 +
     2.1 --- a/src/core/android/SDL_android.c	Sun Aug 27 18:36:54 2017 -0700
     2.2 +++ b/src/core/android/SDL_android.c	Sun Aug 27 18:43:52 2017 -0700
     2.3 @@ -91,6 +91,14 @@
     2.4          JNIEnv* env, jclass jcls,
     2.5          jint device_id);
     2.6  
     2.7 +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeAddHaptic)(
     2.8 +        JNIEnv* env, jclass jcls,
     2.9 +        jint device_id, jstring device_name);
    2.10 +
    2.11 +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeRemoveHaptic)(
    2.12 +        JNIEnv* env, jclass jcls,
    2.13 +        jint device_id);
    2.14 +
    2.15  JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
    2.16          JNIEnv* env, jclass jcls);
    2.17  
    2.18 @@ -121,6 +129,9 @@
    2.19          JNIEnv* env, jclass jcls,
    2.20          jfloat x, jfloat y, jfloat z);
    2.21  
    2.22 +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
    2.23 +        JNIEnv* env, jclass jcls);
    2.24 +
    2.25  JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
    2.26          JNIEnv* env, jclass cls);
    2.27  
    2.28 @@ -187,7 +198,10 @@
    2.29  static jmethodID midSendMessage;
    2.30  static jmethodID midShowTextInput;
    2.31  static jmethodID midIsScreenKeyboardShown;
    2.32 -static jmethodID midGetSystemServiceFromUiThread;
    2.33 +static jmethodID midClipboardSetText;
    2.34 +static jmethodID midClipboardGetText;
    2.35 +static jmethodID midClipboardHasText;
    2.36 +
    2.37  
    2.38  /* static fields */
    2.39  static jfieldID fidSeparateMouseAndTouch;
    2.40 @@ -269,8 +283,12 @@
    2.41                                  "showTextInput", "(IIII)Z");
    2.42      midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    2.43                                  "isScreenKeyboardShown","()Z");
    2.44 -    midGetSystemServiceFromUiThread = (*mEnv)->GetMethodID(mEnv, mActivityClass,
    2.45 -                                "getSystemServiceFromUiThread", "(Ljava/lang/String;)Ljava/lang/Object;");
    2.46 +    midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    2.47 +                                "clipboardSetText", "(Ljava/lang/String;)V");
    2.48 +    midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    2.49 +                                "clipboardGetText", "()Ljava/lang/String;");
    2.50 +    midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
    2.51 +                                "clipboardHasText", "()Z");
    2.52  
    2.53      bHasNewData = SDL_FALSE;
    2.54  
    2.55 @@ -279,7 +297,8 @@
    2.56         !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose ||
    2.57         !midPollInputDevices || !midPollHapticDevices || !midHapticRun ||
    2.58         !midSetActivityTitle || !midSetOrientation || !midGetContext || !midInputGetInputDeviceIds ||
    2.59 -       !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown || !midGetSystemServiceFromUiThread) {
    2.60 +       !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown || 
    2.61 +       !midClipboardSetText || !midClipboardGetText || !midClipboardHasText) {
    2.62          __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
    2.63      }
    2.64  
    2.65 @@ -366,7 +385,7 @@
    2.66      return Android_RemoveJoystick(device_id);
    2.67  }
    2.68  
    2.69 -JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeAddHaptic(
    2.70 +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeAddHaptic)(
    2.71      JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
    2.72  {
    2.73      int retval;
    2.74 @@ -379,7 +398,7 @@
    2.75      return retval;
    2.76  }
    2.77  
    2.78 -JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeRemoveHaptic(
    2.79 +JNIEXPORT jint JNICALL SDL_JAVA_INTERFACE(nativeRemoveHaptic)(
    2.80      JNIEnv* env, jclass jcls, jint device_id)
    2.81  {
    2.82      return Android_RemoveHaptic(device_id);
    2.83 @@ -492,6 +511,13 @@
    2.84      bHasNewData = SDL_TRUE;
    2.85  }
    2.86  
    2.87 +/* Clipboard */
    2.88 +JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
    2.89 +                                    JNIEnv* env, jclass jcls)
    2.90 +{
    2.91 +    SDL_SendClipboardUpdate();
    2.92 +}
    2.93 +
    2.94  /* Low memory */
    2.95  JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
    2.96                                      JNIEnv* env, jclass cls)
    2.97 @@ -1363,118 +1389,41 @@
    2.98      return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
    2.99  }
   2.100  
   2.101 -/* returns a new global reference which needs to be released later */
   2.102 -static jobject Android_JNI_GetSystemServiceObject(const char* name)
   2.103 -{
   2.104 -    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   2.105 -    JNIEnv* env = Android_JNI_GetEnv();
   2.106 -    jobject retval = NULL;
   2.107 -    jstring service;
   2.108 -    jobject context;
   2.109 -    jobject manager;
   2.110 -
   2.111 -    if (!LocalReferenceHolder_Init(&refs, env)) {
   2.112 -        LocalReferenceHolder_Cleanup(&refs);
   2.113 -        return NULL;
   2.114 -    }
   2.115 -
   2.116 -    service = (*env)->NewStringUTF(env, name);
   2.117 -
   2.118 -    /* context = SDLActivity.getContext(); */
   2.119 -    context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
   2.120 -
   2.121 -    manager = (*env)->CallObjectMethod(env, context, midGetSystemServiceFromUiThread, service);
   2.122 -
   2.123 -    (*env)->DeleteLocalRef(env, service);
   2.124 -
   2.125 -    retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
   2.126 -    LocalReferenceHolder_Cleanup(&refs);
   2.127 -    return retval;
   2.128 -}
   2.129 -
   2.130 -#define SETUP_CLIPBOARD(error) \
   2.131 -    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
   2.132 -    JNIEnv* env = Android_JNI_GetEnv(); \
   2.133 -    jobject clipboard; \
   2.134 -    if (!LocalReferenceHolder_Init(&refs, env)) { \
   2.135 -        LocalReferenceHolder_Cleanup(&refs); \
   2.136 -        return error; \
   2.137 -    } \
   2.138 -    clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   2.139 -    if (!clipboard) { \
   2.140 -        LocalReferenceHolder_Cleanup(&refs); \
   2.141 -        return error; \
   2.142 -    }
   2.143 -
   2.144 -#define CLEANUP_CLIPBOARD() \
   2.145 -    LocalReferenceHolder_Cleanup(&refs);
   2.146 -
   2.147  int Android_JNI_SetClipboardText(const char* text)
   2.148  {
   2.149 -    /* Watch out for C89 scoping rules because of the macro */
   2.150 -    SETUP_CLIPBOARD(-1)
   2.151 -
   2.152 -    /* Nest the following in a scope to avoid C89 declaration rules triggered by the macro */
   2.153 -    {
   2.154 -        jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   2.155 -        jstring string = (*env)->NewStringUTF(env, text);
   2.156 -        (*env)->CallVoidMethod(env, clipboard, mid, string);
   2.157 -        (*env)->DeleteGlobalRef(env, clipboard);
   2.158 -        (*env)->DeleteLocalRef(env, string);
   2.159 -    }
   2.160 -    CLEANUP_CLIPBOARD();
   2.161 -
   2.162 +    JNIEnv* env = Android_JNI_GetEnv();
   2.163 +    jstring string = (*env)->NewStringUTF(env, text);
   2.164 +    (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
   2.165 +    (*env)->DeleteLocalRef(env, string);
   2.166      return 0;
   2.167  }
   2.168  
   2.169  char* Android_JNI_GetClipboardText(void)
   2.170  {
   2.171 -    /* Watch out for C89 scoping rules because of the macro */
   2.172 -    SETUP_CLIPBOARD(SDL_strdup(""))
   2.173 -
   2.174 -    /* Nest the following in a scope to avoid C89 declaration rules triggered by the macro */
   2.175 -    {
   2.176 -        jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
   2.177 -        jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
   2.178 -        (*env)->DeleteGlobalRef(env, clipboard);
   2.179 -        if (sequence) {
   2.180 -            jstring string;
   2.181 -            const char* utf;
   2.182 -            mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
   2.183 -            string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
   2.184 -            utf = (*env)->GetStringUTFChars(env, string, 0);
   2.185 -            if (utf) {
   2.186 -                char* text = SDL_strdup(utf);
   2.187 -                (*env)->ReleaseStringUTFChars(env, string, utf);
   2.188 -
   2.189 -                CLEANUP_CLIPBOARD();
   2.190 -
   2.191 -                return text;
   2.192 -            }
   2.193 +    JNIEnv* env = Android_JNI_GetEnv();
   2.194 +    char* text = NULL;
   2.195 +    jstring string;
   2.196 +    
   2.197 +    string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
   2.198 +    if (string) {
   2.199 +        const char* utf = (*env)->GetStringUTFChars(env, string, 0);
   2.200 +        if (utf) {
   2.201 +            text = SDL_strdup(utf);
   2.202 +            (*env)->ReleaseStringUTFChars(env, string, utf);
   2.203          }
   2.204 +        (*env)->DeleteLocalRef(env, string);
   2.205      }
   2.206 -    CLEANUP_CLIPBOARD();
   2.207 -
   2.208 -    return SDL_strdup("");
   2.209 +    
   2.210 +    return (text == NULL) ? SDL_strdup("") : text;
   2.211  }
   2.212  
   2.213  SDL_bool Android_JNI_HasClipboardText(void)
   2.214  {
   2.215 -    jmethodID mid;
   2.216 -    jboolean has;
   2.217 -    /* Watch out for C89 scoping rules because of the macro */
   2.218 -    SETUP_CLIPBOARD(SDL_FALSE)
   2.219 -
   2.220 -    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
   2.221 -    has = (*env)->CallBooleanMethod(env, clipboard, mid);
   2.222 -    (*env)->DeleteGlobalRef(env, clipboard);
   2.223 -
   2.224 -    CLEANUP_CLIPBOARD();
   2.225 -
   2.226 -    return has ? SDL_TRUE : SDL_FALSE;
   2.227 +    JNIEnv* env = Android_JNI_GetEnv();
   2.228 +    jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
   2.229 +    return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
   2.230  }
   2.231  
   2.232 -
   2.233  /* returns 0 on success or -1 on error (others undefined then)
   2.234   * returns truthy or falsy value in plugged, charged and battery
   2.235   * returns the value in seconds and percent or -1 if not available