src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 14 Aug 2017 14:14:45 -0700
changeset 11292 df399ea01ee5
parent 11271 ab3f2402a777
child 11355 6185fb86f046
permissions -rw-r--r--
Fixed bug 3753 - Android : load methodID during initialization

Sylvain

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