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