src/core/android/SDL_android.c
author Manuel Alfayate Corchete
Sun, 17 Jan 2021 21:33:51 +0100
changeset 14743 c3b1b429b80b
parent 14640 b2b3343a310d
permissions -rw-r--r--
[KMS/DRM] Fix for bug #5468: corruption on dynamic cursor changing caused by wrong buffer size.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 #include "SDL_stdinc.h"
    24 #include "SDL_atomic.h"
    25 #include "SDL_hints.h"
    26 #include "SDL_main.h"
    27 #include "SDL_timer.h"
    28 
    29 #ifdef __ANDROID__
    30 
    31 #include "SDL_system.h"
    32 #include "SDL_android.h"
    33 
    34 #include "keyinfotable.h"
    35 
    36 #include "../../events/SDL_events_c.h"
    37 #include "../../video/android/SDL_androidkeyboard.h"
    38 #include "../../video/android/SDL_androidmouse.h"
    39 #include "../../video/android/SDL_androidtouch.h"
    40 #include "../../video/android/SDL_androidvideo.h"
    41 #include "../../video/android/SDL_androidwindow.h"
    42 #include "../../joystick/android/SDL_sysjoystick_c.h"
    43 #include "../../haptic/android/SDL_syshaptic_c.h"
    44 
    45 #include <android/log.h>
    46 #include <android/configuration.h>
    47 #include <android/asset_manager_jni.h>
    48 #include <sys/system_properties.h>
    49 #include <pthread.h>
    50 #include <sys/types.h>
    51 #include <unistd.h>
    52 #include <dlfcn.h>
    53 
    54 #define SDL_JAVA_PREFIX                                 org_libsdl_app
    55 #define CONCAT1(prefix, class, function)                CONCAT2(prefix, class, function)
    56 #define CONCAT2(prefix, class, function)                Java_ ## prefix ## _ ## class ## _ ## function
    57 #define SDL_JAVA_INTERFACE(function)                    CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
    58 #define SDL_JAVA_AUDIO_INTERFACE(function)              CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function)
    59 #define SDL_JAVA_CONTROLLER_INTERFACE(function)         CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
    60 #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function)   CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
    61 
    62 /* Audio encoding definitions */
    63 #define ENCODING_PCM_8BIT   3
    64 #define ENCODING_PCM_16BIT  2
    65 #define ENCODING_PCM_FLOAT  4
    66 
    67 /* Java class SDLActivity */
    68 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
    69         JNIEnv *env, jclass cls);
    70 
    71 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
    72         JNIEnv *env, jclass cls,
    73         jstring library, jstring function, jobject array);
    74 
    75 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
    76         JNIEnv *env, jclass jcls,
    77         jstring filename);
    78 
    79 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)(
    80         JNIEnv *env, jclass jcls,
    81         jint surfaceWidth, jint surfaceHeight,
    82         jint deviceWidth, jint deviceHeight, jint format, jfloat rate);
    83 
    84 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
    85         JNIEnv *env, jclass cls);
    86 
    87 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(
    88         JNIEnv *env, jclass jcls);
    89 
    90 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
    91         JNIEnv *env, jclass jcls);
    92 
    93 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
    94         JNIEnv *env, jclass jcls);
    95 
    96 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
    97         JNIEnv *env, jclass jcls,
    98         jint keycode);
    99 
   100 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
   101         JNIEnv *env, jclass jcls,
   102         jint keycode);
   103 
   104 JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
   105         JNIEnv *env, jclass jcls);
   106 
   107 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
   108         JNIEnv *env, jclass jcls);
   109 
   110 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
   111         JNIEnv *env, jclass jcls,
   112         jint touch_device_id_in, jint pointer_finger_id_in,
   113         jint action, jfloat x, jfloat y, jfloat p);
   114 
   115 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
   116         JNIEnv *env, jclass jcls,
   117         jint button, jint action, jfloat x, jfloat y, jboolean relative);
   118 
   119 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
   120         JNIEnv *env, jclass jcls,
   121         jfloat x, jfloat y, jfloat z);
   122 
   123 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
   124         JNIEnv *env, jclass jcls);
   125 
   126 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
   127         JNIEnv *env, jclass cls);
   128 
   129 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
   130         JNIEnv *env, jclass cls);
   131 
   132 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
   133         JNIEnv *env, jclass cls);
   134 
   135 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
   136         JNIEnv *env, jclass cls);
   137 
   138 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
   139         JNIEnv *env, jclass cls);
   140 
   141 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
   142         JNIEnv *env, jclass cls);
   143 
   144 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
   145         JNIEnv *env, jclass cls, jboolean hasFocus);
   146 
   147 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
   148         JNIEnv *env, jclass cls,
   149         jstring name);
   150 
   151 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
   152         JNIEnv *env, jclass cls,
   153         jstring name, jstring value);
   154 
   155 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)(
   156         JNIEnv *env, jclass cls,
   157         jint orientation);
   158 
   159 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
   160         JNIEnv* env, jclass cls,
   161         jint touchId, jstring name);
   162 
   163 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
   164         JNIEnv* env, jclass cls,
   165         jint requestCode, jboolean result);
   166 
   167 static JNINativeMethod SDLActivity_tab[] = {
   168     { "nativeSetupJNI",             "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) },
   169     { "nativeRunMain",              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) },
   170     { "onNativeDropFile",           "(Ljava/lang/String;)V", SDL_JAVA_INTERFACE(onNativeDropFile) },
   171     { "nativeSetScreenResolution",  "(IIIIIF)V", SDL_JAVA_INTERFACE(nativeSetScreenResolution) },
   172     { "onNativeResize",             "()V", SDL_JAVA_INTERFACE(onNativeResize) },
   173     { "onNativeSurfaceCreated",     "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) },
   174     { "onNativeSurfaceChanged",     "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) },
   175     { "onNativeSurfaceDestroyed",   "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) },
   176     { "onNativeKeyDown",            "(I)V", SDL_JAVA_INTERFACE(onNativeKeyDown) },
   177     { "onNativeKeyUp",              "(I)V", SDL_JAVA_INTERFACE(onNativeKeyUp) },
   178     { "onNativeSoftReturnKey",      "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) },
   179     { "onNativeKeyboardFocusLost",  "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) },
   180     { "onNativeTouch",              "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) },
   181     { "onNativeMouse",              "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) },
   182     { "onNativeAccel",              "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) },
   183     { "onNativeClipboardChanged",   "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) },
   184     { "nativeLowMemory",            "()V", SDL_JAVA_INTERFACE(nativeLowMemory) },
   185     { "onNativeLocaleChanged",      "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) },
   186     { "nativeSendQuit",             "()V", SDL_JAVA_INTERFACE(nativeSendQuit) },
   187     { "nativeQuit",                 "()V", SDL_JAVA_INTERFACE(nativeQuit) },
   188     { "nativePause",                "()V", SDL_JAVA_INTERFACE(nativePause) },
   189     { "nativeResume",               "()V", SDL_JAVA_INTERFACE(nativeResume) },
   190     { "nativeFocusChanged",         "(Z)V", SDL_JAVA_INTERFACE(nativeFocusChanged) },
   191     { "nativeGetHint",              "(Ljava/lang/String;)Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetHint) },
   192     { "nativeSetenv",               "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
   193     { "onNativeOrientationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeOrientationChanged) },
   194     { "nativeAddTouch",             "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) },
   195     { "nativePermissionResult",     "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) }
   196 };
   197 
   198 /* Java class SDLInputConnection */
   199 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
   200         JNIEnv *env, jclass cls,
   201         jstring text, jint newCursorPosition);
   202 
   203 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
   204         JNIEnv *env, jclass cls,
   205         jchar chUnicode);
   206 
   207 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
   208         JNIEnv *env, jclass cls,
   209         jstring text, jint newCursorPosition);
   210 
   211 static JNINativeMethod SDLInputConnection_tab[] = {
   212     { "nativeCommitText",                   "(Ljava/lang/String;I)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText) },
   213     { "nativeGenerateScancodeForUnichar",   "(C)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar) },
   214     { "nativeSetComposingText",             "(Ljava/lang/String;I)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText) }
   215 };
   216 
   217 /* Java class SDLAudioManager */
   218 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
   219         JNIEnv *env, jclass jcls);
   220 
   221 static JNINativeMethod SDLAudioManager_tab[] = {
   222     { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) }
   223 };
   224 
   225 /* Java class SDLControllerManager */
   226 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
   227         JNIEnv *env, jclass jcls);
   228 
   229 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
   230         JNIEnv *env, jclass jcls,
   231         jint device_id, jint keycode);
   232 
   233 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
   234         JNIEnv *env, jclass jcls,
   235         jint device_id, jint keycode);
   236 
   237 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
   238         JNIEnv *env, jclass jcls,
   239         jint device_id, jint axis, jfloat value);
   240 
   241 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
   242         JNIEnv *env, jclass jcls,
   243         jint device_id, jint hat_id, jint x, jint y);
   244 
   245 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
   246         JNIEnv *env, jclass jcls,
   247         jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id,
   248         jboolean is_accelerometer, jint button_mask, jint naxes, jint nhats, jint nballs);
   249 
   250 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
   251         JNIEnv *env, jclass jcls,
   252         jint device_id);
   253 
   254 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
   255         JNIEnv *env, jclass jcls,
   256         jint device_id, jstring device_name);
   257 
   258 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
   259         JNIEnv *env, jclass jcls,
   260         jint device_id);
   261 
   262 static JNINativeMethod SDLControllerManager_tab[] = {
   263     { "nativeSetupJNI",         "()I", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) },
   264     { "onNativePadDown",        "(II)I", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) },
   265     { "onNativePadUp",          "(II)I", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) },
   266     { "onNativeJoy",            "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) },
   267     { "onNativeHat",            "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) },
   268     { "nativeAddJoystick",      "(ILjava/lang/String;Ljava/lang/String;IIZIIII)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) },
   269     { "nativeRemoveJoystick",   "(I)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) },
   270     { "nativeAddHaptic",        "(ILjava/lang/String;)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) },
   271     { "nativeRemoveHaptic",     "(I)I", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) }
   272 };
   273 
   274 
   275 /* Uncomment this to log messages entering and exiting methods in this file */
   276 /* #define DEBUG_JNI */
   277 
   278 static void checkJNIReady(void);
   279 
   280 /*******************************************************************************
   281  This file links the Java side of Android with libsdl
   282 *******************************************************************************/
   283 #include <jni.h>
   284 
   285 
   286 /*******************************************************************************
   287                                Globals
   288 *******************************************************************************/
   289 static pthread_key_t mThreadKey;
   290 static pthread_once_t key_once = PTHREAD_ONCE_INIT;
   291 static JavaVM *mJavaVM = NULL;
   292 
   293 /* Main activity */
   294 static jclass mActivityClass;
   295 
   296 /* method signatures */
   297 static jmethodID midClipboardGetText;
   298 static jmethodID midClipboardHasText;
   299 static jmethodID midClipboardSetText;
   300 static jmethodID midCreateCustomCursor;
   301 static jmethodID midGetContext;
   302 static jmethodID midGetDisplayDPI;
   303 static jmethodID midGetManifestEnvironmentVariables;
   304 static jmethodID midGetNativeSurface;
   305 static jmethodID midInitTouch;
   306 static jmethodID midIsAndroidTV;
   307 static jmethodID midIsChromebook;
   308 static jmethodID midIsDeXMode;
   309 static jmethodID midIsScreenKeyboardShown;
   310 static jmethodID midIsTablet;
   311 static jmethodID midManualBackButton;
   312 static jmethodID midMinimizeWindow;
   313 static jmethodID midOpenURL;
   314 static jmethodID midRequestPermission;
   315 static jmethodID midSendMessage;
   316 static jmethodID midSetActivityTitle;
   317 static jmethodID midSetCustomCursor;
   318 static jmethodID midSetOrientation;
   319 static jmethodID midSetRelativeMouseEnabled;
   320 static jmethodID midSetSurfaceViewFormat;
   321 static jmethodID midSetSystemCursor;
   322 static jmethodID midSetWindowStyle;
   323 static jmethodID midShouldMinimizeOnFocusLoss;
   324 static jmethodID midShowTextInput;
   325 static jmethodID midSupportsRelativeMouse;
   326 
   327 /* audio manager */
   328 static jclass mAudioManagerClass;
   329 
   330 /* method signatures */
   331 static jmethodID midAudioOpen;
   332 static jmethodID midAudioWriteByteBuffer;
   333 static jmethodID midAudioWriteShortBuffer;
   334 static jmethodID midAudioWriteFloatBuffer;
   335 static jmethodID midAudioClose;
   336 static jmethodID midCaptureOpen;
   337 static jmethodID midCaptureReadByteBuffer;
   338 static jmethodID midCaptureReadShortBuffer;
   339 static jmethodID midCaptureReadFloatBuffer;
   340 static jmethodID midCaptureClose;
   341 static jmethodID midAudioSetThreadPriority;
   342 
   343 /* controller manager */
   344 static jclass mControllerManagerClass;
   345 
   346 /* method signatures */
   347 static jmethodID midPollInputDevices;
   348 static jmethodID midPollHapticDevices;
   349 static jmethodID midHapticRun;
   350 static jmethodID midHapticStop;
   351 
   352 /* Accelerometer data storage */
   353 static SDL_DisplayOrientation displayOrientation;
   354 static float fLastAccelerometer[3];
   355 static SDL_bool bHasNewData;
   356 
   357 static SDL_bool bHasEnvironmentVariables;
   358 
   359 static SDL_atomic_t bPermissionRequestPending;
   360 static SDL_bool bPermissionRequestResult;
   361 
   362 /* Android AssetManager */
   363 static void Internal_Android_Create_AssetManager(void);
   364 static void Internal_Android_Destroy_AssetManager(void);
   365 static AAssetManager *asset_manager = NULL;
   366 static jobject javaAssetManagerRef = 0;
   367 
   368 /*******************************************************************************
   369                  Functions called by JNI
   370 *******************************************************************************/
   371 
   372 /* From http://developer.android.com/guide/practices/jni.html
   373  * All threads are Linux threads, scheduled by the kernel.
   374  * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   375  * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   376  * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   377  * and cannot make JNI calls.
   378  * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   379  * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   380  * is a no-op.
   381  * Note: You can call this function any number of times for the same thread, there's no harm in it
   382  */
   383 
   384 /* From http://developer.android.com/guide/practices/jni.html
   385  * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   386  * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   387  * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   388  * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   389  * Note: The destructor is not called unless the stored value is != NULL
   390  * Note: You can call this function any number of times for the same thread, there's no harm in it
   391  *       (except for some lost CPU cycles)
   392  */
   393 
   394 /* Set local storage value */
   395 static int
   396 Android_JNI_SetEnv(JNIEnv *env) {
   397     int status = pthread_setspecific(mThreadKey, env);
   398     if (status < 0) {
   399         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed pthread_setspecific() in Android_JNI_SetEnv() (err=%d)", status);
   400     }
   401     return status;
   402 }
   403 
   404 /* Get local storage value */
   405 JNIEnv* Android_JNI_GetEnv(void)
   406 {
   407     /* Get JNIEnv from the Thread local storage */
   408     JNIEnv *env = pthread_getspecific(mThreadKey);
   409     if (env == NULL) {
   410         /* If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread() */
   411         int status;
   412 
   413         /* There should be a JVM */
   414         if (mJavaVM == NULL) {
   415             __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM");
   416             return NULL;
   417         }
   418 
   419         /* Attach the current thread to the JVM and get a JNIEnv.
   420          * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */
   421         status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   422         if (status < 0) {
   423             __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status);
   424             return NULL;
   425         }
   426 
   427         /* Save JNIEnv into the Thread local storage */
   428         if (Android_JNI_SetEnv(env) < 0) {
   429             return NULL;
   430         }
   431     }
   432 
   433     return env;
   434 }
   435 
   436 /* Set up an external thread for using JNI with Android_JNI_GetEnv() */
   437 int Android_JNI_SetupThread(void)
   438 {
   439     JNIEnv *env;
   440     int status;
   441 
   442     /* There should be a JVM */
   443     if (mJavaVM == NULL) {
   444         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM");
   445         return 0;
   446     }
   447 
   448     /* Attach the current thread to the JVM and get a JNIEnv.
   449      * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */
   450     status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   451     if (status < 0) {
   452         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status);
   453         return 0;
   454     }
   455 
   456     /* Save JNIEnv into the Thread local storage */
   457     if (Android_JNI_SetEnv(env) < 0) {
   458         return 0;
   459     }
   460 
   461     return 1;
   462 }
   463 
   464 /* Destructor called for each thread where mThreadKey is not NULL */
   465 static void
   466 Android_JNI_ThreadDestroyed(void *value)
   467 {
   468     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   469     JNIEnv *env = (JNIEnv *) value;
   470     if (env != NULL) {
   471         (*mJavaVM)->DetachCurrentThread(mJavaVM);
   472         Android_JNI_SetEnv(NULL);
   473     }
   474 }
   475 
   476 /* Creation of local storage mThreadKey */
   477 static void
   478 Android_JNI_CreateKey(void)
   479 {
   480     int status = pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed);
   481     if (status < 0) {
   482         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_key_create() (err=%d)", status);
   483     }
   484 }
   485 
   486 static void
   487 Android_JNI_CreateKey_once(void)
   488 {
   489     int status = pthread_once(&key_once, Android_JNI_CreateKey);
   490     if (status < 0) {
   491         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_once() (err=%d)", status);
   492     }
   493 }
   494 
   495 static void
   496 register_methods(JNIEnv *env, const char *classname, JNINativeMethod *methods, int nb)
   497 {
   498     jclass clazz = (*env)->FindClass(env, classname);
   499     if (clazz == NULL || (*env)->RegisterNatives(env, clazz, methods, nb) < 0) {
   500         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to register methods of %s", classname);
   501         return;
   502     }
   503 }
   504 
   505 /* Library init */
   506 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
   507 {
   508     mJavaVM = vm;
   509     JNIEnv *env = NULL;
   510 
   511     if ((*mJavaVM)->GetEnv(mJavaVM, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
   512         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to get JNI Env");
   513         return JNI_VERSION_1_4;
   514     }
   515 
   516     register_methods(env, "org/libsdl/app/SDLActivity", SDLActivity_tab, SDL_arraysize(SDLActivity_tab));
   517     register_methods(env, "org/libsdl/app/SDLInputConnection", SDLInputConnection_tab, SDL_arraysize(SDLInputConnection_tab));
   518     register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab));
   519     register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab));
   520 
   521     return JNI_VERSION_1_4;
   522 }
   523 
   524 void checkJNIReady(void)
   525 {
   526     if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
   527         /* We aren't fully initialized, let's just return. */
   528         return;
   529     }
   530 
   531     SDL_SetMainReady();
   532 }
   533 
   534 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
   535 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
   536 {
   537     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
   538 
   539     /*
   540      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   541      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   542      */
   543     Android_JNI_CreateKey_once();
   544 
   545     /* Save JNIEnv of SDLActivity */
   546     Android_JNI_SetEnv(env);
   547 
   548     if (mJavaVM == NULL) {
   549         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM");
   550     }
   551 
   552     /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'.
   553      * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. )
   554      */
   555     if (Android_ActivityMutex == NULL) {
   556         Android_ActivityMutex = SDL_CreateMutex(); /* Could this be created twice if onCreate() is called a second time ? */
   557     }
   558 
   559     if (Android_ActivityMutex == NULL) {
   560         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex");
   561     }
   562 
   563 
   564     Android_PauseSem = SDL_CreateSemaphore(0);
   565     if (Android_PauseSem == NULL) {
   566         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_PauseSem semaphore");
   567     }
   568 
   569     Android_ResumeSem = SDL_CreateSemaphore(0);
   570     if (Android_ResumeSem == NULL) {
   571         __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ResumeSem semaphore");
   572     }
   573 
   574     mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls));
   575 
   576     midClipboardGetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardGetText", "()Ljava/lang/String;");
   577     midClipboardHasText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardHasText", "()Z");
   578     midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V");
   579     midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I");
   580     midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext","()Landroid/content/Context;");
   581     midGetDisplayDPI = (*env)->GetStaticMethodID(env, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
   582     midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z");
   583     midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface","()Landroid/view/Surface;");
   584     midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V");
   585     midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV","()Z");
   586     midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z");
   587     midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z");
   588     midIsScreenKeyboardShown = (*env)->GetStaticMethodID(env, mActivityClass, "isScreenKeyboardShown","()Z");
   589     midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z");
   590     midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
   591     midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow","()V");
   592     midOpenURL = (*env)->GetStaticMethodID(env, mActivityClass, "openURL", "(Ljava/lang/String;)I");
   593     midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V");
   594     midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
   595     midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle","(Ljava/lang/String;)Z");
   596     midSetCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setCustomCursor", "(I)Z");
   597     midSetOrientation = (*env)->GetStaticMethodID(env, mActivityClass, "setOrientation","(IIZLjava/lang/String;)V");
   598     midSetRelativeMouseEnabled = (*env)->GetStaticMethodID(env, mActivityClass, "setRelativeMouseEnabled", "(Z)Z");
   599     midSetSurfaceViewFormat = (*env)->GetStaticMethodID(env, mActivityClass, "setSurfaceViewFormat","(I)V");
   600     midSetSystemCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setSystemCursor", "(I)Z");
   601     midSetWindowStyle = (*env)->GetStaticMethodID(env, mActivityClass, "setWindowStyle","(Z)V");
   602     midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss","()Z");
   603     midShowTextInput =  (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
   604     midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
   605 
   606     if (!midClipboardGetText ||
   607         !midClipboardHasText ||
   608         !midClipboardSetText ||
   609         !midCreateCustomCursor ||
   610         !midGetContext ||
   611         !midGetDisplayDPI ||
   612         !midGetManifestEnvironmentVariables ||
   613         !midGetNativeSurface ||
   614         !midInitTouch ||
   615         !midIsAndroidTV ||
   616         !midIsChromebook ||
   617         !midIsDeXMode ||
   618         !midIsScreenKeyboardShown ||
   619         !midIsTablet ||
   620         !midManualBackButton ||
   621         !midMinimizeWindow ||
   622         !midOpenURL ||
   623         !midRequestPermission ||
   624         !midSendMessage ||
   625         !midSetActivityTitle ||
   626         !midSetCustomCursor ||
   627         !midSetOrientation ||
   628         !midSetRelativeMouseEnabled ||
   629         !midSetSurfaceViewFormat ||
   630         !midSetSystemCursor ||
   631         !midSetWindowStyle ||
   632         !midShouldMinimizeOnFocusLoss ||
   633         !midShowTextInput ||
   634         !midSupportsRelativeMouse) {
   635         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
   636     }
   637 
   638     checkJNIReady();
   639 }
   640 
   641 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
   642 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
   643 {
   644     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
   645 
   646     mAudioManagerClass = (jclass)((*env)->NewGlobalRef(env, cls));
   647 
   648     midAudioOpen = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   649                                 "audioOpen", "(IIII)[I");
   650     midAudioWriteByteBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   651                                 "audioWriteByteBuffer", "([B)V");
   652     midAudioWriteShortBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   653                                 "audioWriteShortBuffer", "([S)V");
   654     midAudioWriteFloatBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   655                                 "audioWriteFloatBuffer", "([F)V");
   656     midAudioClose = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   657                                 "audioClose", "()V");
   658     midCaptureOpen = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   659                                 "captureOpen", "(IIII)[I");
   660     midCaptureReadByteBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   661                                 "captureReadByteBuffer", "([BZ)I");
   662     midCaptureReadShortBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   663                                 "captureReadShortBuffer", "([SZ)I");
   664     midCaptureReadFloatBuffer = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   665                                 "captureReadFloatBuffer", "([FZ)I");
   666     midCaptureClose = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   667                                 "captureClose", "()V");
   668     midAudioSetThreadPriority = (*env)->GetStaticMethodID(env, mAudioManagerClass,
   669                                 "audioSetThreadPriority", "(ZI)V");
   670 
   671     if (!midAudioOpen || !midAudioWriteByteBuffer || !midAudioWriteShortBuffer || !midAudioWriteFloatBuffer || !midAudioClose ||
   672        !midCaptureOpen || !midCaptureReadByteBuffer || !midCaptureReadShortBuffer || !midCaptureReadFloatBuffer || !midCaptureClose || !midAudioSetThreadPriority) {
   673         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
   674     }
   675 
   676     checkJNIReady();
   677 }
   678 
   679 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
   680 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
   681 {
   682     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
   683 
   684     mControllerManagerClass = (jclass)((*env)->NewGlobalRef(env, cls));
   685 
   686     midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
   687                                 "pollInputDevices", "()V");
   688     midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
   689                                 "pollHapticDevices", "()V");
   690     midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass,
   691                                 "hapticRun", "(IFI)V");
   692     midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass,
   693                                 "hapticStop", "(I)V");
   694 
   695     if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticStop) {
   696         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
   697     }
   698 
   699     checkJNIReady();
   700 }
   701 
   702 /* SDL main function prototype */
   703 typedef int (*SDL_main_func)(int argc, char *argv[]);
   704 
   705 /* Start up the SDL app */
   706 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, jstring library, jstring function, jobject array)
   707 {
   708     int status = -1;
   709     const char *library_file;
   710     void *library_handle;
   711 
   712     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
   713 
   714     /* Save JNIEnv of SDLThread */
   715     Android_JNI_SetEnv(env);
   716 
   717     library_file = (*env)->GetStringUTFChars(env, library, NULL);
   718     library_handle = dlopen(library_file, RTLD_GLOBAL);
   719 
   720     if (!library_handle) {
   721         /* When deploying android app bundle format uncompressed native libs may not extract from apk to filesystem.
   722            In this case we should use lib name without path. https://bugzilla.libsdl.org/show_bug.cgi?id=4739 */
   723         const char *library_name = SDL_strrchr(library_file, '/');
   724         if (library_name && *library_name) {
   725             library_name += 1;
   726             library_handle = dlopen(library_name, RTLD_GLOBAL);
   727         }
   728     }
   729 
   730     if (library_handle) {
   731         const char *function_name;
   732         SDL_main_func SDL_main;
   733 
   734         function_name = (*env)->GetStringUTFChars(env, function, NULL);
   735         SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
   736         if (SDL_main) {
   737             int i;
   738             int argc;
   739             int len;
   740             char **argv;
   741             SDL_bool isstack;
   742 
   743             /* Prepare the arguments. */
   744             len = (*env)->GetArrayLength(env, array);
   745             argv = SDL_small_alloc(char *, 1 + len + 1, &isstack);  /* !!! FIXME: check for NULL */
   746             argc = 0;
   747             /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
   748                https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
   749              */
   750             argv[argc++] = SDL_strdup("app_process");
   751             for (i = 0; i < len; ++i) {
   752                 const char *utf;
   753                 char *arg = NULL;
   754                 jstring string = (*env)->GetObjectArrayElement(env, array, i);
   755                 if (string) {
   756                     utf = (*env)->GetStringUTFChars(env, string, 0);
   757                     if (utf) {
   758                         arg = SDL_strdup(utf);
   759                         (*env)->ReleaseStringUTFChars(env, string, utf);
   760                     }
   761                     (*env)->DeleteLocalRef(env, string);
   762                 }
   763                 if (!arg) {
   764                     arg = SDL_strdup("");
   765                 }
   766                 argv[argc++] = arg;
   767             }
   768             argv[argc] = NULL;
   769 
   770 
   771             /* Run the application. */
   772             status = SDL_main(argc, argv);
   773 
   774             /* Release the arguments. */
   775             for (i = 0; i < argc; ++i) {
   776                 SDL_free(argv[i]);
   777             }
   778             SDL_small_free(argv, isstack);
   779 
   780         } else {
   781             __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
   782         }
   783         (*env)->ReleaseStringUTFChars(env, function, function_name);
   784 
   785         dlclose(library_handle);
   786 
   787     } else {
   788         __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
   789     }
   790     (*env)->ReleaseStringUTFChars(env, library, library_file);
   791 
   792     /* This is a Java thread, it doesn't need to be Detached from the JVM.
   793      * Set to mThreadKey value to NULL not to call pthread_create destructor 'Android_JNI_ThreadDestroyed' */
   794     Android_JNI_SetEnv(NULL);
   795 
   796     /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
   797     /* exit(status); */
   798 
   799     return status;
   800 }
   801 
   802 /* Drop file */
   803 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
   804                                     JNIEnv *env, jclass jcls,
   805                                     jstring filename)
   806 {
   807     const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
   808     SDL_SendDropFile(NULL, path);
   809     (*env)->ReleaseStringUTFChars(env, filename, path);
   810     SDL_SendDropComplete(NULL);
   811 }
   812 
   813 /* Lock / Unlock Mutex */
   814 void Android_ActivityMutex_Lock() {
   815     SDL_LockMutex(Android_ActivityMutex);
   816 }
   817 
   818 void Android_ActivityMutex_Unlock() {
   819     SDL_UnlockMutex(Android_ActivityMutex);
   820 }
   821 
   822 /* Lock the Mutex when the Activity is in its 'Running' state */
   823 void Android_ActivityMutex_Lock_Running() {
   824     int pauseSignaled = 0;
   825     int resumeSignaled = 0;
   826 
   827 retry:
   828 
   829     SDL_LockMutex(Android_ActivityMutex);
   830 
   831     pauseSignaled = SDL_SemValue(Android_PauseSem);
   832     resumeSignaled = SDL_SemValue(Android_ResumeSem);
   833 
   834     if (pauseSignaled > resumeSignaled) {
   835         SDL_UnlockMutex(Android_ActivityMutex);
   836         SDL_Delay(50);
   837         goto retry;
   838     }
   839 }
   840 
   841 /* Set screen resolution */
   842 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)(
   843                                     JNIEnv *env, jclass jcls,
   844                                     jint surfaceWidth, jint surfaceHeight,
   845                                     jint deviceWidth, jint deviceHeight, jint format, jfloat rate)
   846 {
   847     SDL_LockMutex(Android_ActivityMutex);
   848 
   849     Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, format, rate);
   850 
   851     SDL_UnlockMutex(Android_ActivityMutex);
   852 }
   853 
   854 /* Resize */
   855 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
   856                                     JNIEnv *env, jclass jcls)
   857 {
   858     SDL_LockMutex(Android_ActivityMutex);
   859 
   860     if (Android_Window)
   861     {
   862         Android_SendResize(Android_Window);
   863     }
   864 
   865     SDL_UnlockMutex(Android_ActivityMutex);
   866 }
   867 
   868 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)(
   869                                     JNIEnv *env, jclass jcls,
   870                                     jint orientation)
   871 {
   872     SDL_LockMutex(Android_ActivityMutex);
   873 
   874     displayOrientation = (SDL_DisplayOrientation)orientation;
   875 
   876     if (Android_Window)
   877     {
   878         SDL_VideoDisplay *display = SDL_GetDisplay(0);
   879         SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation);
   880     }
   881 
   882     SDL_UnlockMutex(Android_ActivityMutex);
   883 }
   884 
   885 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
   886         JNIEnv* env, jclass cls,
   887         jint touchId, jstring name)
   888 {
   889     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
   890 
   891     SDL_AddTouch((SDL_TouchID) touchId, SDL_TOUCH_DEVICE_DIRECT, utfname);
   892 
   893     (*env)->ReleaseStringUTFChars(env, name, utfname);
   894 }
   895 
   896 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
   897         JNIEnv* env, jclass cls,
   898         jint requestCode, jboolean result)
   899 {
   900     bPermissionRequestResult = result;
   901     SDL_AtomicSet(&bPermissionRequestPending, SDL_FALSE);
   902 }
   903 
   904 /* Paddown */
   905 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
   906                                     JNIEnv *env, jclass jcls,
   907                                     jint device_id, jint keycode)
   908 {
   909     return Android_OnPadDown(device_id, keycode);
   910 }
   911 
   912 /* Padup */
   913 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
   914                                     JNIEnv *env, jclass jcls,
   915                                     jint device_id, jint keycode)
   916 {
   917     return Android_OnPadUp(device_id, keycode);
   918 }
   919 
   920 /* Joy */
   921 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
   922                                     JNIEnv *env, jclass jcls,
   923                                     jint device_id, jint axis, jfloat value)
   924 {
   925     Android_OnJoy(device_id, axis, value);
   926 }
   927 
   928 /* POV Hat */
   929 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
   930                                     JNIEnv *env, jclass jcls,
   931                                     jint device_id, jint hat_id, jint x, jint y)
   932 {
   933     Android_OnHat(device_id, hat_id, x, y);
   934 }
   935 
   936 
   937 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
   938                                     JNIEnv *env, jclass jcls,
   939                                     jint device_id, jstring device_name, jstring device_desc,
   940                                     jint vendor_id, jint product_id, jboolean is_accelerometer,
   941                                     jint button_mask, jint naxes, jint nhats, jint nballs)
   942 {
   943     int retval;
   944     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
   945     const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
   946 
   947     retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer ? SDL_TRUE : SDL_FALSE, button_mask, naxes, nhats, nballs);
   948 
   949     (*env)->ReleaseStringUTFChars(env, device_name, name);
   950     (*env)->ReleaseStringUTFChars(env, device_desc, desc);
   951 
   952     return retval;
   953 }
   954 
   955 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
   956                                     JNIEnv *env, jclass jcls,
   957                                     jint device_id)
   958 {
   959     return Android_RemoveJoystick(device_id);
   960 }
   961 
   962 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
   963     JNIEnv *env, jclass jcls, jint device_id, jstring device_name)
   964 {
   965     int retval;
   966     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
   967 
   968     retval = Android_AddHaptic(device_id, name);
   969 
   970     (*env)->ReleaseStringUTFChars(env, device_name, name);
   971 
   972     return retval;
   973 }
   974 
   975 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
   976     JNIEnv *env, jclass jcls, jint device_id)
   977 {
   978     return Android_RemoveHaptic(device_id);
   979 }
   980 
   981 /* Called from surfaceCreated() */
   982 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls)
   983 {
   984     SDL_LockMutex(Android_ActivityMutex);
   985 
   986     if (Android_Window)
   987     {
   988         SDL_WindowData *data = (SDL_WindowData *) Android_Window->driverdata;
   989 
   990         data->native_window = Android_JNI_GetNativeWindow();
   991         if (data->native_window == NULL) {
   992             SDL_SetError("Could not fetch native window from UI thread");
   993         }
   994     }
   995 
   996     SDL_UnlockMutex(Android_ActivityMutex);
   997 }
   998 
   999 /* Called from surfaceChanged() */
  1000 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, jclass jcls)
  1001 {
  1002     SDL_LockMutex(Android_ActivityMutex);
  1003 
  1004     if (Android_Window)
  1005     {
  1006         SDL_VideoDevice *_this = SDL_GetVideoDevice();
  1007         SDL_WindowData  *data  = (SDL_WindowData *) Android_Window->driverdata;
  1008 
  1009         /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
  1010         if (data->egl_surface == EGL_NO_SURFACE) {
  1011             data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
  1012         }
  1013 
  1014         /* GL Context handling is done in the event loop because this function is run from the Java thread */
  1015     }
  1016 
  1017     SDL_UnlockMutex(Android_ActivityMutex);
  1018 }
  1019 
  1020 /* Called from surfaceDestroyed() */
  1021 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv *env, jclass jcls)
  1022 {
  1023     int nb_attempt = 50;
  1024 
  1025 retry:
  1026 
  1027     SDL_LockMutex(Android_ActivityMutex);
  1028 
  1029     if (Android_Window)
  1030     {
  1031         SDL_VideoDevice *_this = SDL_GetVideoDevice();
  1032         SDL_WindowData  *data  = (SDL_WindowData *) Android_Window->driverdata;
  1033 
  1034         /* Wait for Main thread being paused and context un-activated to release 'egl_surface' */
  1035         if (! data->backup_done) {
  1036             nb_attempt -= 1;
  1037             if (nb_attempt == 0) {
  1038                 SDL_SetError("Try to release egl_surface with context probably still active");
  1039             } else {
  1040                 SDL_UnlockMutex(Android_ActivityMutex);
  1041                 SDL_Delay(10);
  1042                 goto retry;
  1043             }
  1044         }
  1045 
  1046         if (data->egl_surface != EGL_NO_SURFACE) {
  1047             SDL_EGL_DestroySurface(_this, data->egl_surface);
  1048             data->egl_surface = EGL_NO_SURFACE;
  1049         }
  1050 
  1051         if (data->native_window) {
  1052             ANativeWindow_release(data->native_window);
  1053             data->native_window = NULL;
  1054         }
  1055 
  1056         /* GL Context handling is done in the event loop because this function is run from the Java thread */
  1057     }
  1058 
  1059     SDL_UnlockMutex(Android_ActivityMutex);
  1060 }
  1061 
  1062 /* Keydown */
  1063 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
  1064                                     JNIEnv *env, jclass jcls,
  1065                                     jint keycode)
  1066 {
  1067     Android_OnKeyDown(keycode);
  1068 }
  1069 
  1070 /* Keyup */
  1071 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
  1072                                     JNIEnv *env, jclass jcls,
  1073                                     jint keycode)
  1074 {
  1075     Android_OnKeyUp(keycode);
  1076 }
  1077 
  1078 /* Virtual keyboard return key might stop text input */
  1079 JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
  1080                                     JNIEnv *env, jclass jcls)
  1081 {
  1082     if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, SDL_FALSE)) {
  1083         SDL_StopTextInput();
  1084         return JNI_TRUE;
  1085     }
  1086     return JNI_FALSE;
  1087 }
  1088 
  1089 /* Keyboard Focus Lost */
  1090 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
  1091                                     JNIEnv *env, jclass jcls)
  1092 {
  1093     /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
  1094     SDL_StopTextInput();
  1095 }
  1096 
  1097 
  1098 /* Touch */
  1099 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
  1100                                     JNIEnv *env, jclass jcls,
  1101                                     jint touch_device_id_in, jint pointer_finger_id_in,
  1102                                     jint action, jfloat x, jfloat y, jfloat p)
  1103 {
  1104     SDL_LockMutex(Android_ActivityMutex);
  1105 
  1106     Android_OnTouch(Android_Window, touch_device_id_in, pointer_finger_id_in, action, x, y, p);
  1107 
  1108     SDL_UnlockMutex(Android_ActivityMutex);
  1109 }
  1110 
  1111 /* Mouse */
  1112 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
  1113                                     JNIEnv *env, jclass jcls,
  1114                                     jint button, jint action, jfloat x, jfloat y, jboolean relative)
  1115 {
  1116     SDL_LockMutex(Android_ActivityMutex);
  1117 
  1118     Android_OnMouse(Android_Window, button, action, x, y, relative);
  1119 
  1120     SDL_UnlockMutex(Android_ActivityMutex);
  1121 }
  1122 
  1123 /* Accelerometer */
  1124 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
  1125                                     JNIEnv *env, jclass jcls,
  1126                                     jfloat x, jfloat y, jfloat z)
  1127 {
  1128     fLastAccelerometer[0] = x;
  1129     fLastAccelerometer[1] = y;
  1130     fLastAccelerometer[2] = z;
  1131     bHasNewData = SDL_TRUE;
  1132 }
  1133 
  1134 /* Clipboard */
  1135 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
  1136                                     JNIEnv *env, jclass jcls)
  1137 {
  1138     SDL_SendClipboardUpdate();
  1139 }
  1140 
  1141 /* Low memory */
  1142 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
  1143                                     JNIEnv *env, jclass cls)
  1144 {
  1145     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
  1146 }
  1147 
  1148 /* Locale
  1149  * requires android:configChanges="layoutDirection|locale" in AndroidManifest.xml */
  1150 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
  1151                                     JNIEnv *env, jclass cls)
  1152 {
  1153     SDL_SendAppEvent(SDL_LOCALECHANGED);
  1154 }
  1155 
  1156 
  1157 /* Send Quit event to "SDLThread" thread */
  1158 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
  1159                                     JNIEnv *env, jclass cls)
  1160 {
  1161     /* Discard previous events. The user should have handled state storage
  1162      * in SDL_APP_WILLENTERBACKGROUND. After nativeSendQuit() is called, no
  1163      * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
  1164     SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
  1165     /* Inject a SDL_QUIT event */
  1166     SDL_SendQuit();
  1167     SDL_SendAppEvent(SDL_APP_TERMINATING);
  1168     /* Robustness: clear any pending Pause */
  1169     while (SDL_SemTryWait(Android_PauseSem) == 0) {
  1170         /* empty */
  1171     }
  1172     /* Resume the event loop so that the app can catch SDL_QUIT which
  1173      * should now be the top event in the event queue. */
  1174     SDL_SemPost(Android_ResumeSem);
  1175 }
  1176 
  1177 /* Activity ends */
  1178 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
  1179                                     JNIEnv *env, jclass cls)
  1180 {
  1181     const char *str;
  1182 
  1183     if (Android_ActivityMutex) {
  1184         SDL_DestroyMutex(Android_ActivityMutex);
  1185         Android_ActivityMutex = NULL;
  1186     }
  1187 
  1188     if (Android_PauseSem) {
  1189         SDL_DestroySemaphore(Android_PauseSem);
  1190         Android_PauseSem = NULL;
  1191     }
  1192 
  1193     if (Android_ResumeSem) {
  1194         SDL_DestroySemaphore(Android_ResumeSem);
  1195         Android_ResumeSem = NULL;
  1196     }
  1197 
  1198     Internal_Android_Destroy_AssetManager();
  1199 
  1200     str = SDL_GetError();
  1201     if (str && str[0]) {
  1202         __android_log_print(ANDROID_LOG_ERROR, "SDL", "SDLActivity thread ends (error=%s)", str);
  1203     } else {
  1204         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDLActivity thread ends");
  1205     }
  1206 }
  1207 
  1208 /* Pause */
  1209 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
  1210                                     JNIEnv *env, jclass cls)
  1211 {
  1212     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
  1213 
  1214     /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself.
  1215      * Sometimes 2 pauses can be queued (eg pause/resume/pause), so it's always increased. */
  1216     SDL_SemPost(Android_PauseSem);
  1217 }
  1218 
  1219 /* Resume */
  1220 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
  1221                                     JNIEnv *env, jclass cls)
  1222 {
  1223     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
  1224 
  1225     /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
  1226      * We can't restore the GL Context here because it needs to be done on the SDL main thread
  1227      * and this function will be called from the Java thread instead.
  1228      */
  1229     SDL_SemPost(Android_ResumeSem);
  1230 }
  1231 
  1232 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
  1233                                     JNIEnv *env, jclass cls, jboolean hasFocus)
  1234 {
  1235     SDL_LockMutex(Android_ActivityMutex);
  1236 
  1237     if (Android_Window) {
  1238         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()");
  1239         SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_WINDOWEVENT_FOCUS_GAINED : SDL_WINDOWEVENT_FOCUS_LOST), 0, 0);
  1240     }
  1241 
  1242     SDL_UnlockMutex(Android_ActivityMutex);
  1243 }
  1244 
  1245 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
  1246                                     JNIEnv *env, jclass cls,
  1247                                     jstring text, jint newCursorPosition)
  1248 {
  1249     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
  1250 
  1251     SDL_SendKeyboardText(utftext);
  1252 
  1253     (*env)->ReleaseStringUTFChars(env, text, utftext);
  1254 }
  1255 
  1256 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
  1257                                     JNIEnv *env, jclass cls,
  1258                                     jchar chUnicode)
  1259 {
  1260     SDL_Scancode code = SDL_SCANCODE_UNKNOWN;
  1261     uint16_t mod = 0;
  1262 
  1263     /* We do not care about bigger than 127. */
  1264     if (chUnicode < 127) {
  1265         AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode];
  1266         code = info.code;
  1267         mod = info.mod;
  1268     }
  1269 
  1270     if (mod & KMOD_SHIFT) {
  1271         /* If character uses shift, press shift down */
  1272         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
  1273     }
  1274 
  1275     /* send a keydown and keyup even for the character */
  1276     SDL_SendKeyboardKey(SDL_PRESSED, code);
  1277     SDL_SendKeyboardKey(SDL_RELEASED, code);
  1278 
  1279     if (mod & KMOD_SHIFT) {
  1280         /* If character uses shift, press shift back up */
  1281         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
  1282     }
  1283 }
  1284 
  1285 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
  1286                                     JNIEnv *env, jclass cls,
  1287                                     jstring text, jint newCursorPosition)
  1288 {
  1289     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
  1290 
  1291     SDL_SendEditingText(utftext, 0, 0);
  1292 
  1293     (*env)->ReleaseStringUTFChars(env, text, utftext);
  1294 }
  1295 
  1296 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
  1297                                     JNIEnv *env, jclass cls,
  1298                                     jstring name)
  1299 {
  1300     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
  1301     const char *hint = SDL_GetHint(utfname);
  1302 
  1303     jstring result = (*env)->NewStringUTF(env, hint);
  1304     (*env)->ReleaseStringUTFChars(env, name, utfname);
  1305 
  1306     return result;
  1307 }
  1308 
  1309 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
  1310                                     JNIEnv *env, jclass cls,
  1311                                     jstring name, jstring value)
  1312 {
  1313     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
  1314     const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
  1315 
  1316     SDL_setenv(utfname, utfvalue, 1);
  1317 
  1318     (*env)->ReleaseStringUTFChars(env, name, utfname);
  1319     (*env)->ReleaseStringUTFChars(env, value, utfvalue);
  1320 
  1321 }
  1322 
  1323 /*******************************************************************************
  1324              Functions called by SDL into Java
  1325 *******************************************************************************/
  1326 
  1327 static SDL_atomic_t s_active;
  1328 struct LocalReferenceHolder
  1329 {
  1330     JNIEnv *m_env;
  1331     const char *m_func;
  1332 };
  1333 
  1334 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
  1335 {
  1336     struct LocalReferenceHolder refholder;
  1337     refholder.m_env = NULL;
  1338     refholder.m_func = func;
  1339 #ifdef DEBUG_JNI
  1340     SDL_Log("Entering function %s", func);
  1341 #endif
  1342     return refholder;
  1343 }
  1344 
  1345 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
  1346 {
  1347     const int capacity = 16;
  1348     if ((*env)->PushLocalFrame(env, capacity) < 0) {
  1349         SDL_SetError("Failed to allocate enough JVM local references");
  1350         return SDL_FALSE;
  1351     }
  1352     SDL_AtomicIncRef(&s_active);
  1353     refholder->m_env = env;
  1354     return SDL_TRUE;
  1355 }
  1356 
  1357 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
  1358 {
  1359 #ifdef DEBUG_JNI
  1360     SDL_Log("Leaving function %s", refholder->m_func);
  1361 #endif
  1362     if (refholder->m_env) {
  1363         JNIEnv *env = refholder->m_env;
  1364         (*env)->PopLocalFrame(env, NULL);
  1365         SDL_AtomicDecRef(&s_active);
  1366     }
  1367 }
  1368 
  1369 ANativeWindow* Android_JNI_GetNativeWindow(void)
  1370 {
  1371     ANativeWindow *anw = NULL;
  1372     jobject s;
  1373     JNIEnv *env = Android_JNI_GetEnv();
  1374 
  1375     s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
  1376     if (s) {
  1377         anw = ANativeWindow_fromSurface(env, s);
  1378         (*env)->DeleteLocalRef(env, s);
  1379     }
  1380 
  1381     return anw;
  1382 }
  1383 
  1384 void Android_JNI_SetSurfaceViewFormat(int format)
  1385 {
  1386     JNIEnv *env = Android_JNI_GetEnv();
  1387     int new_format = 0;
  1388 
  1389     /* Format from android/native_window.h,
  1390      * convert to temporary arbitrary values,
  1391      * then to java PixelFormat */
  1392     if (format == WINDOW_FORMAT_RGBA_8888) {
  1393         new_format = 1;
  1394     } else if (format == WINDOW_FORMAT_RGBX_8888) {
  1395         new_format = 2;
  1396     } else if (format == WINDOW_FORMAT_RGB_565) {
  1397         /* Default */
  1398         new_format = 0;
  1399     }
  1400 
  1401     (*env)->CallStaticVoidMethod(env, mActivityClass, midSetSurfaceViewFormat, new_format);
  1402 }
  1403 
  1404 void Android_JNI_SetActivityTitle(const char *title)
  1405 {
  1406     JNIEnv *env = Android_JNI_GetEnv();
  1407 
  1408     jstring jtitle = (*env)->NewStringUTF(env, title);
  1409     (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetActivityTitle, jtitle);
  1410     (*env)->DeleteLocalRef(env, jtitle);
  1411 }
  1412 
  1413 void Android_JNI_SetWindowStyle(SDL_bool fullscreen)
  1414 {
  1415     JNIEnv *env = Android_JNI_GetEnv();
  1416     (*env)->CallStaticVoidMethod(env, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
  1417 }
  1418 
  1419 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
  1420 {
  1421     JNIEnv *env = Android_JNI_GetEnv();
  1422 
  1423     jstring jhint = (*env)->NewStringUTF(env, (hint ? hint : ""));
  1424     (*env)->CallStaticVoidMethod(env, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
  1425     (*env)->DeleteLocalRef(env, jhint);
  1426 }
  1427 
  1428 void Android_JNI_MinizeWindow()
  1429 {
  1430     JNIEnv *env = Android_JNI_GetEnv();
  1431     (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow);
  1432 }
  1433 
  1434 SDL_bool Android_JNI_ShouldMinimizeOnFocusLoss()
  1435 {
  1436     JNIEnv *env = Android_JNI_GetEnv();
  1437     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss);
  1438 }
  1439 
  1440 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
  1441 {
  1442     int i;
  1443     SDL_bool retval = SDL_FALSE;
  1444 
  1445     if (bHasNewData) {
  1446         for (i = 0; i < 3; ++i) {
  1447             values[i] = fLastAccelerometer[i];
  1448         }
  1449         bHasNewData = SDL_FALSE;
  1450         retval = SDL_TRUE;
  1451     }
  1452 
  1453     return retval;
  1454 }
  1455 
  1456 /*
  1457  * Audio support
  1458  */
  1459 static int audioBufferFormat = 0;
  1460 static jobject audioBuffer = NULL;
  1461 static void *audioBufferPinned = NULL;
  1462 static int captureBufferFormat = 0;
  1463 static jobject captureBuffer = NULL;
  1464 
  1465 int Android_JNI_OpenAudioDevice(int iscapture, SDL_AudioSpec *spec)
  1466 {
  1467     int audioformat;
  1468     jobject jbufobj = NULL;
  1469     jobject result;
  1470     int *resultElements;
  1471     jboolean isCopy;
  1472 
  1473     JNIEnv *env = Android_JNI_GetEnv();
  1474 
  1475     switch (spec->format) {
  1476     case AUDIO_U8:
  1477         audioformat = ENCODING_PCM_8BIT;
  1478         break;
  1479     case AUDIO_S16:
  1480         audioformat = ENCODING_PCM_16BIT;
  1481         break;
  1482     case AUDIO_F32:
  1483         audioformat = ENCODING_PCM_FLOAT;
  1484         break;
  1485     default:
  1486         return SDL_SetError("Unsupported audio format: 0x%x", spec->format);
  1487     }
  1488 
  1489     if (iscapture) {
  1490         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
  1491         result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midCaptureOpen, spec->freq, audioformat, spec->channels, spec->samples);
  1492     } else {
  1493         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
  1494         result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midAudioOpen, spec->freq, audioformat, spec->channels, spec->samples);
  1495     }
  1496     if (result == NULL) {
  1497         /* Error during audio initialization, error printed from Java */
  1498         return SDL_SetError("Java-side initialization failed");
  1499     }
  1500 
  1501     if ((*env)->GetArrayLength(env, (jintArray)result) != 4) {
  1502         return SDL_SetError("Unexpected results from Java, expected 4, got %d", (*env)->GetArrayLength(env, (jintArray)result));
  1503     }
  1504     isCopy = JNI_FALSE;
  1505     resultElements = (*env)->GetIntArrayElements(env, (jintArray)result, &isCopy);
  1506     spec->freq = resultElements[0];
  1507     audioformat = resultElements[1];
  1508     switch (audioformat) {
  1509     case ENCODING_PCM_8BIT:
  1510         spec->format = AUDIO_U8;
  1511         break;
  1512     case ENCODING_PCM_16BIT:
  1513         spec->format = AUDIO_S16;
  1514         break;
  1515     case ENCODING_PCM_FLOAT:
  1516         spec->format = AUDIO_F32;
  1517         break;
  1518     default:
  1519         return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
  1520     }
  1521     spec->channels = resultElements[2];
  1522     spec->samples = resultElements[3];
  1523     (*env)->ReleaseIntArrayElements(env, (jintArray)result, resultElements, JNI_ABORT);
  1524     (*env)->DeleteLocalRef(env, result);
  1525 
  1526     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
  1527      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
  1528     switch (audioformat) {
  1529     case ENCODING_PCM_8BIT:
  1530         {
  1531             jbyteArray audioBufferLocal = (*env)->NewByteArray(env, spec->samples * spec->channels);
  1532             if (audioBufferLocal) {
  1533                 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
  1534                 (*env)->DeleteLocalRef(env, audioBufferLocal);
  1535             }
  1536         }
  1537         break;
  1538     case ENCODING_PCM_16BIT:
  1539         {
  1540             jshortArray audioBufferLocal = (*env)->NewShortArray(env, spec->samples * spec->channels);
  1541             if (audioBufferLocal) {
  1542                 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
  1543                 (*env)->DeleteLocalRef(env, audioBufferLocal);
  1544             }
  1545         }
  1546         break;
  1547     case ENCODING_PCM_FLOAT:
  1548         {
  1549             jfloatArray audioBufferLocal = (*env)->NewFloatArray(env, spec->samples * spec->channels);
  1550             if (audioBufferLocal) {
  1551                 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
  1552                 (*env)->DeleteLocalRef(env, audioBufferLocal);
  1553             }
  1554         }
  1555         break;
  1556     default:
  1557         return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
  1558     }
  1559 
  1560     if (jbufobj == NULL) {
  1561         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer");
  1562         return SDL_OutOfMemory();
  1563     }
  1564 
  1565     if (iscapture) {
  1566         captureBufferFormat = audioformat;
  1567         captureBuffer = jbufobj;
  1568     } else {
  1569         audioBufferFormat = audioformat;
  1570         audioBuffer = jbufobj;
  1571     }
  1572 
  1573     if (!iscapture) {
  1574         isCopy = JNI_FALSE;
  1575 
  1576         switch (audioformat) {
  1577         case ENCODING_PCM_8BIT:
  1578             audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
  1579             break;
  1580         case ENCODING_PCM_16BIT:
  1581             audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
  1582             break;
  1583         case ENCODING_PCM_FLOAT:
  1584             audioBufferPinned = (*env)->GetFloatArrayElements(env, (jfloatArray)audioBuffer, &isCopy);
  1585             break;
  1586         default:
  1587             return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
  1588         }
  1589     }
  1590     return 0;
  1591 }
  1592 
  1593 SDL_DisplayOrientation Android_JNI_GetDisplayOrientation(void)
  1594 {
  1595     return displayOrientation;
  1596 }
  1597 
  1598 int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi)
  1599 {
  1600     JNIEnv *env = Android_JNI_GetEnv();
  1601 
  1602     jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI);
  1603     jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj);
  1604 
  1605     jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F");
  1606     jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F");
  1607     jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I");
  1608 
  1609     float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi);
  1610     float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi);
  1611     int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi);
  1612 
  1613 
  1614     (*env)->DeleteLocalRef(env, jDisplayObj);
  1615     (*env)->DeleteLocalRef(env, jDisplayClass);
  1616 
  1617     if (ddpi) {
  1618         *ddpi = (float)nativeDdpi;
  1619     }
  1620     if (xdpi) {
  1621         *xdpi = nativeXdpi;
  1622     }
  1623     if (ydpi) {
  1624         *ydpi = nativeYdpi;
  1625     }
  1626 
  1627     return 0;
  1628 }
  1629 
  1630 void * Android_JNI_GetAudioBuffer(void)
  1631 {
  1632     return audioBufferPinned;
  1633 }
  1634 
  1635 void Android_JNI_WriteAudioBuffer(void)
  1636 {
  1637     JNIEnv *env = Android_JNI_GetEnv();
  1638 
  1639     switch (audioBufferFormat) {
  1640     case ENCODING_PCM_8BIT:
  1641         (*env)->ReleaseByteArrayElements(env, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
  1642         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
  1643         break;
  1644     case ENCODING_PCM_16BIT:
  1645         (*env)->ReleaseShortArrayElements(env, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
  1646         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
  1647         break;
  1648     case ENCODING_PCM_FLOAT:
  1649         (*env)->ReleaseFloatArrayElements(env, (jfloatArray)audioBuffer, (jfloat *)audioBufferPinned, JNI_COMMIT);
  1650         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioWriteFloatBuffer, (jfloatArray)audioBuffer);
  1651         break;
  1652     default:
  1653         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: unhandled audio buffer format");
  1654         break;
  1655     }
  1656 
  1657     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
  1658 }
  1659 
  1660 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
  1661 {
  1662     JNIEnv *env = Android_JNI_GetEnv();
  1663     jboolean isCopy = JNI_FALSE;
  1664     jint br = -1;
  1665 
  1666     switch (captureBufferFormat) {
  1667     case ENCODING_PCM_8BIT:
  1668         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
  1669         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
  1670         if (br > 0) {
  1671             jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
  1672             SDL_memcpy(buffer, ptr, br);
  1673             (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, ptr, JNI_ABORT);
  1674         }
  1675         break;
  1676     case ENCODING_PCM_16BIT:
  1677         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / sizeof(Sint16)));
  1678         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
  1679         if (br > 0) {
  1680             jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
  1681             br *= sizeof(Sint16);
  1682             SDL_memcpy(buffer, ptr, br);
  1683             (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, ptr, JNI_ABORT);
  1684         }
  1685         break;
  1686     case ENCODING_PCM_FLOAT:
  1687         SDL_assert((*env)->GetArrayLength(env, (jfloatArray)captureBuffer) == (buflen / sizeof(float)));
  1688         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_TRUE);
  1689         if (br > 0) {
  1690             jfloat *ptr = (*env)->GetFloatArrayElements(env, (jfloatArray)captureBuffer, &isCopy);
  1691             br *= sizeof(float);
  1692             SDL_memcpy(buffer, ptr, br);
  1693             (*env)->ReleaseFloatArrayElements(env, (jfloatArray)captureBuffer, ptr, JNI_ABORT);
  1694         }
  1695         break;
  1696     default:
  1697         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: unhandled capture buffer format");
  1698         break;
  1699     }
  1700     return br;
  1701 }
  1702 
  1703 void Android_JNI_FlushCapturedAudio(void)
  1704 {
  1705     JNIEnv *env = Android_JNI_GetEnv();
  1706 #if 0  /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
  1707     switch (captureBufferFormat) {
  1708     case ENCODING_PCM_8BIT:
  1709         {
  1710             const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
  1711             while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
  1712         }
  1713         break;
  1714     case ENCODING_PCM_16BIT:
  1715         {
  1716             const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
  1717             while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
  1718         }
  1719         break;
  1720     case ENCODING_PCM_FLOAT:
  1721         {
  1722             const jint len = (*env)->GetArrayLength(env, (jfloatArray)captureBuffer);
  1723             while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
  1724         }
  1725         break;
  1726     default:
  1727         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: flushing unhandled capture buffer format");
  1728         break;
  1729     }
  1730 #else
  1731     switch (captureBufferFormat) {
  1732     case ENCODING_PCM_8BIT:
  1733         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
  1734         break;
  1735     case ENCODING_PCM_16BIT:
  1736         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
  1737         break;
  1738     case ENCODING_PCM_FLOAT:
  1739         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_FALSE);
  1740         break;
  1741     default:
  1742         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: flushing unhandled capture buffer format");
  1743         break;
  1744     }
  1745 #endif
  1746 }
  1747 
  1748 void Android_JNI_CloseAudioDevice(const int iscapture)
  1749 {
  1750     JNIEnv *env = Android_JNI_GetEnv();
  1751 
  1752     if (iscapture) {
  1753         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
  1754         if (captureBuffer) {
  1755             (*env)->DeleteGlobalRef(env, captureBuffer);
  1756             captureBuffer = NULL;
  1757         }
  1758     } else {
  1759         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
  1760         if (audioBuffer) {
  1761             (*env)->DeleteGlobalRef(env, audioBuffer);
  1762             audioBuffer = NULL;
  1763             audioBufferPinned = NULL;
  1764         }
  1765     }
  1766 }
  1767 
  1768 void Android_JNI_AudioSetThreadPriority(int iscapture, int device_id)
  1769 {
  1770     JNIEnv *env = Android_JNI_GetEnv();
  1771     (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, iscapture, device_id);
  1772 }
  1773 
  1774 /* Test for an exception and call SDL_SetError with its detail if one occurs */
  1775 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
  1776 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
  1777 {
  1778     JNIEnv *env = Android_JNI_GetEnv();
  1779     jthrowable exception;
  1780 
  1781     /* Detect mismatch LocalReferenceHolder_Init/Cleanup */
  1782     SDL_assert(SDL_AtomicGet(&s_active) > 0);
  1783 
  1784     exception = (*env)->ExceptionOccurred(env);
  1785     if (exception != NULL) {
  1786         jmethodID mid;
  1787 
  1788         /* Until this happens most JNI operations have undefined behaviour */
  1789         (*env)->ExceptionClear(env);
  1790 
  1791         if (!silent) {
  1792             jclass exceptionClass = (*env)->GetObjectClass(env, exception);
  1793             jclass classClass = (*env)->FindClass(env, "java/lang/Class");
  1794             jstring exceptionName;
  1795             const char *exceptionNameUTF8;
  1796             jstring exceptionMessage;
  1797 
  1798             mid = (*env)->GetMethodID(env, classClass, "getName", "()Ljava/lang/String;");
  1799             exceptionName = (jstring)(*env)->CallObjectMethod(env, exceptionClass, mid);
  1800             exceptionNameUTF8 = (*env)->GetStringUTFChars(env, exceptionName, 0);
  1801 
  1802             mid = (*env)->GetMethodID(env, exceptionClass, "getMessage", "()Ljava/lang/String;");
  1803             exceptionMessage = (jstring)(*env)->CallObjectMethod(env, exception, mid);
  1804 
  1805             if (exceptionMessage != NULL) {
  1806                 const char *exceptionMessageUTF8 = (*env)->GetStringUTFChars(env, exceptionMessage, 0);
  1807                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
  1808                 (*env)->ReleaseStringUTFChars(env, exceptionMessage, exceptionMessageUTF8);
  1809             } else {
  1810                 SDL_SetError("%s", exceptionNameUTF8);
  1811             }
  1812 
  1813             (*env)->ReleaseStringUTFChars(env, exceptionName, exceptionNameUTF8);
  1814         }
  1815 
  1816         return SDL_TRUE;
  1817     }
  1818 
  1819     return SDL_FALSE;
  1820 }
  1821 
  1822 static void Internal_Android_Create_AssetManager() {
  1823 
  1824     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1825     JNIEnv *env = Android_JNI_GetEnv();
  1826     jmethodID mid;
  1827     jobject context;
  1828     jobject javaAssetManager;
  1829 
  1830     if (!LocalReferenceHolder_Init(&refs, env)) {
  1831         LocalReferenceHolder_Cleanup(&refs);
  1832         return;
  1833     }
  1834 
  1835     /* context = SDLActivity.getContext(); */
  1836     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  1837 
  1838     /* javaAssetManager = context.getAssets(); */
  1839     mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1840             "getAssets", "()Landroid/content/res/AssetManager;");
  1841     javaAssetManager = (*env)->CallObjectMethod(env, context, mid);
  1842 
  1843     /**
  1844      * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager
  1845      * object.  Note that the caller is responsible for obtaining and holding a VM reference
  1846      * to the jobject to prevent its being garbage collected while the native object is
  1847      * in use.
  1848      */
  1849     javaAssetManagerRef = (*env)->NewGlobalRef(env, javaAssetManager);
  1850     asset_manager = AAssetManager_fromJava(env, javaAssetManagerRef);
  1851 
  1852     if (asset_manager == NULL) {
  1853         (*env)->DeleteGlobalRef(env, javaAssetManagerRef);
  1854         Android_JNI_ExceptionOccurred(SDL_TRUE);
  1855     }
  1856 
  1857     LocalReferenceHolder_Cleanup(&refs);
  1858 }
  1859 
  1860 static void Internal_Android_Destroy_AssetManager() {
  1861     JNIEnv *env = Android_JNI_GetEnv();
  1862 
  1863     if (asset_manager) {
  1864         (*env)->DeleteGlobalRef(env, javaAssetManagerRef);
  1865         asset_manager = NULL;
  1866     }
  1867 }
  1868 
  1869 int Android_JNI_FileOpen(SDL_RWops *ctx,
  1870         const char *fileName, const char *mode)
  1871 {
  1872     AAsset *asset = NULL;
  1873     ctx->hidden.androidio.asset = NULL;
  1874 
  1875     if (asset_manager == NULL) {
  1876         Internal_Android_Create_AssetManager();
  1877     }
  1878 
  1879     if (asset_manager == NULL) {
  1880         return -1;
  1881     }
  1882 
  1883     asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN);
  1884     if (asset == NULL) {
  1885         return -1;
  1886     }
  1887 
  1888 
  1889     ctx->hidden.androidio.asset = (void*) asset;
  1890     return 0;
  1891 }
  1892 
  1893 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
  1894         size_t size, size_t maxnum)
  1895 {
  1896     size_t result;
  1897     AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
  1898     result = AAsset_read(asset, buffer, size * maxnum);
  1899 
  1900     if (result > 0) {
  1901         /* Number of chuncks */
  1902         return (result / size);
  1903     } else {
  1904         /* Error or EOF */
  1905         return result;
  1906     }
  1907 }
  1908 
  1909 size_t Android_JNI_FileWrite(SDL_RWops *ctx, const void *buffer,
  1910         size_t size, size_t num)
  1911 {
  1912     SDL_SetError("Cannot write to Android package filesystem");
  1913     return 0;
  1914 }
  1915 
  1916 Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
  1917 {
  1918     off64_t result;
  1919     AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
  1920     result = AAsset_getLength64(asset);
  1921     return result;
  1922 }
  1923 
  1924 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
  1925 {
  1926     off64_t result;
  1927     AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
  1928     result = AAsset_seek64(asset, offset, whence);
  1929     return result;
  1930 }
  1931 
  1932 int Android_JNI_FileClose(SDL_RWops *ctx)
  1933 {
  1934     AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
  1935     AAsset_close(asset);
  1936     return 0;
  1937 }
  1938 
  1939 int Android_JNI_SetClipboardText(const char *text)
  1940 {
  1941     JNIEnv *env = Android_JNI_GetEnv();
  1942     jstring string = (*env)->NewStringUTF(env, text);
  1943     (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
  1944     (*env)->DeleteLocalRef(env, string);
  1945     return 0;
  1946 }
  1947 
  1948 char* Android_JNI_GetClipboardText(void)
  1949 {
  1950     JNIEnv *env = Android_JNI_GetEnv();
  1951     char *text = NULL;
  1952     jstring string;
  1953 
  1954     string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
  1955     if (string) {
  1956         const char *utf = (*env)->GetStringUTFChars(env, string, 0);
  1957         if (utf) {
  1958             text = SDL_strdup(utf);
  1959             (*env)->ReleaseStringUTFChars(env, string, utf);
  1960         }
  1961         (*env)->DeleteLocalRef(env, string);
  1962     }
  1963 
  1964     return (text == NULL) ? SDL_strdup("") : text;
  1965 }
  1966 
  1967 SDL_bool Android_JNI_HasClipboardText(void)
  1968 {
  1969     JNIEnv *env = Android_JNI_GetEnv();
  1970     jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
  1971     return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
  1972 }
  1973 
  1974 /* returns 0 on success or -1 on error (others undefined then)
  1975  * returns truthy or falsy value in plugged, charged and battery
  1976  * returns the value in seconds and percent or -1 if not available
  1977  */
  1978 int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent)
  1979 {
  1980     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1981     JNIEnv *env = Android_JNI_GetEnv();
  1982     jmethodID mid;
  1983     jobject context;
  1984     jstring action;
  1985     jclass cls;
  1986     jobject filter;
  1987     jobject intent;
  1988     jstring iname;
  1989     jmethodID imid;
  1990     jstring bname;
  1991     jmethodID bmid;
  1992     if (!LocalReferenceHolder_Init(&refs, env)) {
  1993         LocalReferenceHolder_Cleanup(&refs);
  1994         return -1;
  1995     }
  1996 
  1997 
  1998     /* context = SDLActivity.getContext(); */
  1999     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2000 
  2001     action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  2002 
  2003     cls = (*env)->FindClass(env, "android/content/IntentFilter");
  2004 
  2005     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  2006     filter = (*env)->NewObject(env, cls, mid, action);
  2007 
  2008     (*env)->DeleteLocalRef(env, action);
  2009 
  2010     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  2011     intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  2012 
  2013     (*env)->DeleteLocalRef(env, filter);
  2014 
  2015     cls = (*env)->GetObjectClass(env, intent);
  2016 
  2017     imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  2018 
  2019     /* Watch out for C89 scoping rules because of the macro */
  2020 #define GET_INT_EXTRA(var, key) \
  2021     int var; \
  2022     iname = (*env)->NewStringUTF(env, key); \
  2023     (var) = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  2024     (*env)->DeleteLocalRef(env, iname);
  2025 
  2026     bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  2027 
  2028     /* Watch out for C89 scoping rules because of the macro */
  2029 #define GET_BOOL_EXTRA(var, key) \
  2030     int var; \
  2031     bname = (*env)->NewStringUTF(env, key); \
  2032     (var) = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  2033     (*env)->DeleteLocalRef(env, bname);
  2034 
  2035     if (plugged) {
  2036         /* Watch out for C89 scoping rules because of the macro */
  2037         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
  2038         if (plug == -1) {
  2039             LocalReferenceHolder_Cleanup(&refs);
  2040             return -1;
  2041         }
  2042         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
  2043         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
  2044         *plugged = (0 < plug) ? 1 : 0;
  2045     }
  2046 
  2047     if (charged) {
  2048         /* Watch out for C89 scoping rules because of the macro */
  2049         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
  2050         if (status == -1) {
  2051             LocalReferenceHolder_Cleanup(&refs);
  2052             return -1;
  2053         }
  2054         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
  2055         *charged = (status == 5) ? 1 : 0;
  2056     }
  2057 
  2058     if (battery) {
  2059         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
  2060         *battery = present ? 1 : 0;
  2061     }
  2062 
  2063     if (seconds) {
  2064         *seconds = -1; /* not possible */
  2065     }
  2066 
  2067     if (percent) {
  2068         int level;
  2069         int scale;
  2070 
  2071         /* Watch out for C89 scoping rules because of the macro */
  2072         {
  2073             GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
  2074             level = level_temp;
  2075         }
  2076         /* Watch out for C89 scoping rules because of the macro */
  2077         {
  2078             GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
  2079             scale = scale_temp;
  2080         }
  2081 
  2082         if ((level == -1) || (scale == -1)) {
  2083             LocalReferenceHolder_Cleanup(&refs);
  2084             return -1;
  2085         }
  2086         *percent = level * 100 / scale;
  2087     }
  2088 
  2089     (*env)->DeleteLocalRef(env, intent);
  2090 
  2091     LocalReferenceHolder_Cleanup(&refs);
  2092     return 0;
  2093 }
  2094 
  2095 /* Add all touch devices */
  2096 void Android_JNI_InitTouch() {
  2097      JNIEnv *env = Android_JNI_GetEnv();
  2098     (*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch);
  2099 }
  2100 
  2101 void Android_JNI_PollInputDevices(void)
  2102 {
  2103     JNIEnv *env = Android_JNI_GetEnv();
  2104     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
  2105 }
  2106 
  2107 void Android_JNI_PollHapticDevices(void)
  2108 {
  2109     JNIEnv *env = Android_JNI_GetEnv();
  2110     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
  2111 }
  2112 
  2113 void Android_JNI_HapticRun(int device_id, float intensity, int length)
  2114 {
  2115     JNIEnv *env = Android_JNI_GetEnv();
  2116     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length);
  2117 }
  2118 
  2119 void Android_JNI_HapticStop(int device_id)
  2120 {
  2121     JNIEnv *env = Android_JNI_GetEnv();
  2122     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id);
  2123 }
  2124 
  2125 /* See SDLActivity.java for constants. */
  2126 #define COMMAND_SET_KEEP_SCREEN_ON    5
  2127 
  2128 /* sends message to be handled on the UI event dispatch thread */
  2129 int Android_JNI_SendMessage(int command, int param)
  2130 {
  2131     JNIEnv *env = Android_JNI_GetEnv();
  2132     jboolean success;
  2133     success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
  2134     return success ? 0 : -1;
  2135 }
  2136 
  2137 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
  2138 {
  2139     Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
  2140 }
  2141 
  2142 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  2143 {
  2144     JNIEnv *env = Android_JNI_GetEnv();
  2145     (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
  2146                                inputRect->x,
  2147                                inputRect->y,
  2148                                inputRect->w,
  2149                                inputRect->h );
  2150 }
  2151 
  2152 void Android_JNI_HideTextInput(void)
  2153 {
  2154     /* has to match Activity constant */
  2155     const int COMMAND_TEXTEDIT_HIDE = 3;
  2156     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  2157 }
  2158 
  2159 SDL_bool Android_JNI_IsScreenKeyboardShown(void)
  2160 {
  2161     JNIEnv *env = Android_JNI_GetEnv();
  2162     jboolean is_shown = 0;
  2163     is_shown = (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsScreenKeyboardShown);
  2164     return is_shown;
  2165 }
  2166 
  2167 
  2168 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
  2169 {
  2170     JNIEnv *env;
  2171     jclass clazz;
  2172     jmethodID mid;
  2173     jobject context;
  2174     jstring title;
  2175     jstring message;
  2176     jintArray button_flags;
  2177     jintArray button_ids;
  2178     jobjectArray button_texts;
  2179     jintArray colors;
  2180     jobject text;
  2181     jint temp;
  2182     int i;
  2183 
  2184     env = Android_JNI_GetEnv();
  2185 
  2186     /* convert parameters */
  2187 
  2188     clazz = (*env)->FindClass(env, "java/lang/String");
  2189 
  2190     title = (*env)->NewStringUTF(env, messageboxdata->title);
  2191     message = (*env)->NewStringUTF(env, messageboxdata->message);
  2192 
  2193     button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  2194     button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
  2195     button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
  2196         clazz, NULL);
  2197     for (i = 0; i < messageboxdata->numbuttons; ++i) {
  2198         const SDL_MessageBoxButtonData *sdlButton;
  2199 
  2200         if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) {
  2201             sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
  2202         } else {
  2203             sdlButton = &messageboxdata->buttons[i];
  2204         }
  2205 
  2206         temp = sdlButton->flags;
  2207         (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
  2208         temp = sdlButton->buttonid;
  2209         (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
  2210         text = (*env)->NewStringUTF(env, sdlButton->text);
  2211         (*env)->SetObjectArrayElement(env, button_texts, i, text);
  2212         (*env)->DeleteLocalRef(env, text);
  2213     }
  2214 
  2215     if (messageboxdata->colorScheme) {
  2216         colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
  2217         for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
  2218             temp = (0xFFU << 24) |
  2219                    (messageboxdata->colorScheme->colors[i].r << 16) |
  2220                    (messageboxdata->colorScheme->colors[i].g << 8) |
  2221                    (messageboxdata->colorScheme->colors[i].b << 0);
  2222             (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
  2223         }
  2224     } else {
  2225         colors = NULL;
  2226     }
  2227 
  2228     (*env)->DeleteLocalRef(env, clazz);
  2229 
  2230     /* context = SDLActivity.getContext(); */
  2231     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2232 
  2233     clazz = (*env)->GetObjectClass(env, context);
  2234 
  2235     mid = (*env)->GetMethodID(env, clazz,
  2236         "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
  2237     *buttonid = (*env)->CallIntMethod(env, context, mid,
  2238         messageboxdata->flags,
  2239         title,
  2240         message,
  2241         button_flags,
  2242         button_ids,
  2243         button_texts,
  2244         colors);
  2245 
  2246     (*env)->DeleteLocalRef(env, context);
  2247     (*env)->DeleteLocalRef(env, clazz);
  2248 
  2249     /* delete parameters */
  2250 
  2251     (*env)->DeleteLocalRef(env, title);
  2252     (*env)->DeleteLocalRef(env, message);
  2253     (*env)->DeleteLocalRef(env, button_flags);
  2254     (*env)->DeleteLocalRef(env, button_ids);
  2255     (*env)->DeleteLocalRef(env, button_texts);
  2256     (*env)->DeleteLocalRef(env, colors);
  2257 
  2258     return 0;
  2259 }
  2260 
  2261 /*
  2262 //////////////////////////////////////////////////////////////////////////////
  2263 //
  2264 // Functions exposed to SDL applications in SDL_system.h
  2265 //////////////////////////////////////////////////////////////////////////////
  2266 */
  2267 
  2268 void *SDL_AndroidGetJNIEnv(void)
  2269 {
  2270     return Android_JNI_GetEnv();
  2271 }
  2272 
  2273 void *SDL_AndroidGetActivity(void)
  2274 {
  2275     /* See SDL_system.h for caveats on using this function. */
  2276 
  2277     JNIEnv *env = Android_JNI_GetEnv();
  2278     if (!env) {
  2279         return NULL;
  2280     }
  2281 
  2282     /* return SDLActivity.getContext(); */
  2283     return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2284 }
  2285 
  2286 int SDL_GetAndroidSDKVersion(void)
  2287 {
  2288     static int sdk_version;
  2289     if (!sdk_version) {
  2290         char sdk[PROP_VALUE_MAX] = {0};
  2291         if (__system_property_get("ro.build.version.sdk", sdk) != 0) {
  2292             sdk_version = SDL_atoi(sdk);
  2293         }
  2294     }
  2295     return sdk_version;
  2296 }
  2297 
  2298 SDL_bool SDL_IsAndroidTablet(void)
  2299 {
  2300     JNIEnv *env = Android_JNI_GetEnv();
  2301     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet);
  2302 }
  2303 
  2304 SDL_bool SDL_IsAndroidTV(void)
  2305 {
  2306     JNIEnv *env = Android_JNI_GetEnv();
  2307     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
  2308 }
  2309 
  2310 SDL_bool SDL_IsChromebook(void)
  2311 {
  2312     JNIEnv *env = Android_JNI_GetEnv();
  2313     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsChromebook);
  2314 }
  2315 
  2316 SDL_bool SDL_IsDeXMode(void)
  2317 {
  2318     JNIEnv *env = Android_JNI_GetEnv();
  2319     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode);
  2320 }
  2321 
  2322 void SDL_AndroidBackButton(void)
  2323 {
  2324     JNIEnv *env = Android_JNI_GetEnv();
  2325     (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton);
  2326 }
  2327 
  2328 const char * SDL_AndroidGetInternalStoragePath(void)
  2329 {
  2330     static char *s_AndroidInternalFilesPath = NULL;
  2331 
  2332     if (!s_AndroidInternalFilesPath) {
  2333         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  2334         jmethodID mid;
  2335         jobject context;
  2336         jobject fileObject;
  2337         jstring pathString;
  2338         const char *path;
  2339 
  2340         JNIEnv *env = Android_JNI_GetEnv();
  2341         if (!LocalReferenceHolder_Init(&refs, env)) {
  2342             LocalReferenceHolder_Cleanup(&refs);
  2343             return NULL;
  2344         }
  2345 
  2346         /* context = SDLActivity.getContext(); */
  2347         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2348         if (!context) {
  2349             SDL_SetError("Couldn't get Android context!");
  2350             LocalReferenceHolder_Cleanup(&refs);
  2351             return NULL;
  2352         }
  2353 
  2354         /* fileObj = context.getFilesDir(); */
  2355         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  2356                 "getFilesDir", "()Ljava/io/File;");
  2357         fileObject = (*env)->CallObjectMethod(env, context, mid);
  2358         if (!fileObject) {
  2359             SDL_SetError("Couldn't get internal directory");
  2360             LocalReferenceHolder_Cleanup(&refs);
  2361             return NULL;
  2362         }
  2363 
  2364         /* path = fileObject.getCanonicalPath(); */
  2365         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  2366                 "getCanonicalPath", "()Ljava/lang/String;");
  2367         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  2368         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
  2369             LocalReferenceHolder_Cleanup(&refs);
  2370             return NULL;
  2371         }
  2372 
  2373         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  2374         s_AndroidInternalFilesPath = SDL_strdup(path);
  2375         (*env)->ReleaseStringUTFChars(env, pathString, path);
  2376 
  2377         LocalReferenceHolder_Cleanup(&refs);
  2378     }
  2379     return s_AndroidInternalFilesPath;
  2380 }
  2381 
  2382 int SDL_AndroidGetExternalStorageState(void)
  2383 {
  2384     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  2385     jmethodID mid;
  2386     jclass cls;
  2387     jstring stateString;
  2388     const char *state;
  2389     int stateFlags;
  2390 
  2391     JNIEnv *env = Android_JNI_GetEnv();
  2392     if (!LocalReferenceHolder_Init(&refs, env)) {
  2393         LocalReferenceHolder_Cleanup(&refs);
  2394         return 0;
  2395     }
  2396 
  2397     cls = (*env)->FindClass(env, "android/os/Environment");
  2398     mid = (*env)->GetStaticMethodID(env, cls,
  2399             "getExternalStorageState", "()Ljava/lang/String;");
  2400     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  2401 
  2402     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  2403 
  2404     /* Print an info message so people debugging know the storage state */
  2405     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  2406 
  2407     if (SDL_strcmp(state, "mounted") == 0) {
  2408         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  2409                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  2410     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  2411         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  2412     } else {
  2413         stateFlags = 0;
  2414     }
  2415     (*env)->ReleaseStringUTFChars(env, stateString, state);
  2416 
  2417     LocalReferenceHolder_Cleanup(&refs);
  2418     return stateFlags;
  2419 }
  2420 
  2421 const char * SDL_AndroidGetExternalStoragePath(void)
  2422 {
  2423     static char *s_AndroidExternalFilesPath = NULL;
  2424 
  2425     if (!s_AndroidExternalFilesPath) {
  2426         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  2427         jmethodID mid;
  2428         jobject context;
  2429         jobject fileObject;
  2430         jstring pathString;
  2431         const char *path;
  2432 
  2433         JNIEnv *env = Android_JNI_GetEnv();
  2434         if (!LocalReferenceHolder_Init(&refs, env)) {
  2435             LocalReferenceHolder_Cleanup(&refs);
  2436             return NULL;
  2437         }
  2438 
  2439         /* context = SDLActivity.getContext(); */
  2440         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
  2441 
  2442         /* fileObj = context.getExternalFilesDir(); */
  2443         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  2444                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  2445         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  2446         if (!fileObject) {
  2447             SDL_SetError("Couldn't get external directory");
  2448             LocalReferenceHolder_Cleanup(&refs);
  2449             return NULL;
  2450         }
  2451 
  2452         /* path = fileObject.getAbsolutePath(); */
  2453         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  2454                 "getAbsolutePath", "()Ljava/lang/String;");
  2455         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  2456 
  2457         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  2458         s_AndroidExternalFilesPath = SDL_strdup(path);
  2459         (*env)->ReleaseStringUTFChars(env, pathString, path);
  2460 
  2461         LocalReferenceHolder_Cleanup(&refs);
  2462     }
  2463     return s_AndroidExternalFilesPath;
  2464 }
  2465 
  2466 SDL_bool SDL_AndroidRequestPermission(const char *permission)
  2467 {
  2468     return Android_JNI_RequestPermission(permission);
  2469 }
  2470 
  2471 void Android_JNI_GetManifestEnvironmentVariables(void)
  2472 {
  2473     if (!mActivityClass || !midGetManifestEnvironmentVariables) {
  2474         __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
  2475         return;
  2476     }
  2477 
  2478     if (!bHasEnvironmentVariables) {
  2479         JNIEnv *env = Android_JNI_GetEnv();
  2480         SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
  2481         if (ret) {
  2482             bHasEnvironmentVariables = SDL_TRUE;
  2483         }
  2484     }
  2485 }
  2486 
  2487 int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
  2488 {
  2489     JNIEnv *env = Android_JNI_GetEnv();
  2490     int custom_cursor = 0;
  2491     jintArray pixels;
  2492     pixels = (*env)->NewIntArray(env, surface->w * surface->h);
  2493     if (pixels) {
  2494         (*env)->SetIntArrayRegion(env, pixels, 0, surface->w * surface->h, (int *)surface->pixels);
  2495         custom_cursor = (*env)->CallStaticIntMethod(env, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y);
  2496         (*env)->DeleteLocalRef(env, pixels);
  2497     } else {
  2498         SDL_OutOfMemory();
  2499     }
  2500     return custom_cursor;
  2501 }
  2502 
  2503 
  2504 SDL_bool Android_JNI_SetCustomCursor(int cursorID)
  2505 {
  2506     JNIEnv *env = Android_JNI_GetEnv();
  2507     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetCustomCursor, cursorID);
  2508 }
  2509 
  2510 SDL_bool Android_JNI_SetSystemCursor(int cursorID)
  2511 {
  2512     JNIEnv *env = Android_JNI_GetEnv();
  2513     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetSystemCursor, cursorID);
  2514 }
  2515 
  2516 SDL_bool Android_JNI_SupportsRelativeMouse(void)
  2517 {
  2518     JNIEnv *env = Android_JNI_GetEnv();
  2519     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSupportsRelativeMouse);
  2520 }
  2521 
  2522 SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled)
  2523 {
  2524     JNIEnv *env = Android_JNI_GetEnv();
  2525     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
  2526 }
  2527 
  2528 SDL_bool Android_JNI_RequestPermission(const char *permission)
  2529 {
  2530     JNIEnv *env = Android_JNI_GetEnv();
  2531 	const int requestCode = 1;
  2532 
  2533 	/* Wait for any pending request on another thread */
  2534 	while (SDL_AtomicGet(&bPermissionRequestPending) == SDL_TRUE) {
  2535 		SDL_Delay(10);
  2536 	}
  2537 	SDL_AtomicSet(&bPermissionRequestPending, SDL_TRUE);
  2538 
  2539     jstring jpermission = (*env)->NewStringUTF(env, permission);
  2540     (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, requestCode);
  2541     (*env)->DeleteLocalRef(env, jpermission);
  2542 
  2543 	/* Wait for the request to complete */
  2544 	while (SDL_AtomicGet(&bPermissionRequestPending) == SDL_TRUE) {
  2545 		SDL_Delay(10);
  2546 	}
  2547 	return bPermissionRequestResult;
  2548 }
  2549 
  2550 int Android_JNI_GetLocale(char *buf, size_t buflen)
  2551 {
  2552     AConfiguration *cfg;
  2553 
  2554     SDL_assert(buflen > 6);
  2555 
  2556     /* Need to re-create the asset manager if locale has changed (SDL_LOCALECHANGED) */
  2557     Internal_Android_Destroy_AssetManager();
  2558 
  2559     if (asset_manager == NULL) {
  2560         Internal_Android_Create_AssetManager();
  2561     }
  2562 
  2563     if (asset_manager == NULL) {
  2564         return -1;
  2565     }
  2566 
  2567     cfg = AConfiguration_new();
  2568     if (cfg == NULL) {
  2569         return -1;
  2570     }
  2571 
  2572     {
  2573         char language[2] = {};
  2574         char country[2] = {};
  2575         size_t id = 0;
  2576 
  2577         AConfiguration_fromAssetManager(cfg, asset_manager);
  2578         AConfiguration_getLanguage(cfg, language);
  2579         AConfiguration_getCountry(cfg, country);
  2580 
  2581         /* copy language (not null terminated) */
  2582         if (language[0]) {
  2583             buf[id++] = language[0];
  2584             if (language[1]) {
  2585                 buf[id++] = language[1];
  2586             }
  2587         }
  2588 
  2589         buf[id++] = '_';
  2590 
  2591         /* copy country (not null terminated) */
  2592         if (country[0]) {
  2593             buf[id++] = country[0];
  2594             if (country[1]) {
  2595                 buf[id++] = country[1];
  2596             }
  2597         }
  2598         
  2599         buf[id++] = '\0';
  2600         SDL_assert(id <= buflen);
  2601     }
  2602 
  2603     AConfiguration_delete(cfg);
  2604 
  2605     return 0;
  2606 }
  2607 
  2608 int
  2609 Android_JNI_OpenURL(const char *url)
  2610 {
  2611     JNIEnv *env = Android_JNI_GetEnv();
  2612     jstring jurl = (*env)->NewStringUTF(env, url);
  2613     const int ret = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenURL, jurl);
  2614     (*env)->DeleteLocalRef(env, jurl);
  2615     return ret;
  2616 }
  2617 
  2618 #endif /* __ANDROID__ */
  2619 
  2620 /* vi: set ts=4 sw=4 expandtab: */