src/core/android/SDL_android.c
author Sam Lantinga
Fri, 05 Apr 2019 08:15:01 -0700
changeset 12696 3ef8a628853d
parent 12689 d581caf613cd
child 12709 d268ce129edb
permissions -rw-r--r--
Fixed bug 4579 - SDL_android.c s_active not being atomic

Isaias Brunet

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