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