src/core/android/SDL_android.c
author Sylvain Becker <sylvain.becker@gmail.com>
Fri, 28 Jun 2019 16:38:42 +0200
changeset 12910 dd9169424181
parent 12902 50d0c0ade376
child 12955 131ea7dcc225
permissions -rw-r--r--
Android: concurrency issues, make sure Activity is in running State when calling
functions like SDL_CreateWindow, SDL_CreateRenderer, Android_GLES_CreateContext

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