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