src/core/android/SDL_android.c
author Gabriel Jacobo <gabomdq@gmail.com>
Wed, 31 Jul 2013 10:04:59 -0300
changeset 7552 463f78515a07
parent 7516 4a98853e8e4c
child 7564 d2bc997ef5d6
permissions -rw-r--r--
Fix for bug 2001, verify that the AudioTrack is properly initialized

...and fail to initialize the audio backend if it's not!
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 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_config.h"
    22 #include "SDL_stdinc.h"
    23 #include "SDL_assert.h"
    24 #include "SDL_log.h"
    25 
    26 #ifdef __ANDROID__
    27 
    28 #include "SDL_system.h"
    29 #include "SDL_android.h"
    30 #include <EGL/egl.h>
    31 
    32 #include "../../events/SDL_events_c.h"
    33 #include "../../video/android/SDL_androidkeyboard.h"
    34 #include "../../video/android/SDL_androidtouch.h"
    35 #include "../../video/android/SDL_androidvideo.h"
    36 
    37 #include <android/log.h>
    38 #include <pthread.h>
    39 #include <sys/types.h>
    40 #include <unistd.h>
    41 #define LOG_TAG "SDL_android"
    42 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    43 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    44 #define LOGI(...) do {} while (false)
    45 #define LOGE(...) do {} while (false)
    46 
    47 /* Uncomment this to log messages entering and exiting methods in this file */
    48 //#define DEBUG_JNI
    49 
    50 /* Implemented in audio/android/SDL_androidaudio.c */
    51 extern void Android_RunAudioThread();
    52 
    53 static void Android_JNI_ThreadDestroyed(void*);
    54 
    55 /*******************************************************************************
    56  This file links the Java side of Android with libsdl
    57 *******************************************************************************/
    58 #include <jni.h>
    59 #include <android/log.h>
    60 #include <stdbool.h>
    61 
    62 
    63 /*******************************************************************************
    64                                Globals
    65 *******************************************************************************/
    66 static pthread_key_t mThreadKey;
    67 static JavaVM* mJavaVM;
    68 
    69 // Main activity
    70 static jclass mActivityClass;
    71 
    72 // method signatures
    73 static jmethodID midCreateGLContext;
    74 static jmethodID midFlipBuffers;
    75 static jmethodID midAudioInit;
    76 static jmethodID midAudioWriteShortBuffer;
    77 static jmethodID midAudioWriteByteBuffer;
    78 static jmethodID midAudioQuit;
    79 
    80 // Accelerometer data storage
    81 static float fLastAccelerometer[3];
    82 static bool bHasNewData;
    83 
    84 /*******************************************************************************
    85                  Functions called by JNI
    86 *******************************************************************************/
    87 
    88 // Library init
    89 jint JNI_OnLoad(JavaVM* vm, void* reserved)
    90 {
    91     JNIEnv *env;
    92     mJavaVM = vm;
    93     LOGI("JNI_OnLoad called");
    94     if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    95         LOGE("Failed to get the environment using GetEnv()");
    96         return -1;
    97     }
    98     /*
    99      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   100      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   101      */
   102     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
   103         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   104     }
   105     else {
   106         Android_JNI_SetupThread();
   107     }
   108 
   109     return JNI_VERSION_1_4;
   110 }
   111 
   112 // Called before SDL_main() to initialize JNI bindings
   113 void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   114 {
   115     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   116 
   117     Android_JNI_SetupThread();
   118 
   119     mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
   120 
   121     midCreateGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   122                                 "createGLContext","(II[I)Z");
   123     midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   124                                 "flipBuffers","()V");
   125     midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   126                                 "audioInit", "(IZZI)I");
   127     midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   128                                 "audioWriteShortBuffer", "([S)V");
   129     midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   130                                 "audioWriteByteBuffer", "([B)V");
   131     midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   132                                 "audioQuit", "()V");
   133 
   134     bHasNewData = false;
   135 
   136     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   137        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   138         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   139     }
   140     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   141 }
   142 
   143 // Resize
   144 void Java_org_libsdl_app_SDLActivity_onNativeResize(
   145                                     JNIEnv* env, jclass jcls,
   146                                     jint width, jint height, jint format)
   147 {
   148     Android_SetScreenResolution(width, height, format);
   149 }
   150 
   151 // Keydown
   152 void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   153                                     JNIEnv* env, jclass jcls, jint keycode)
   154 {
   155     Android_OnKeyDown(keycode);
   156 }
   157 
   158 // Keyup
   159 void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   160                                     JNIEnv* env, jclass jcls, jint keycode)
   161 {
   162     Android_OnKeyUp(keycode);
   163 }
   164 
   165 // Touch
   166 void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   167                                     JNIEnv* env, jclass jcls,
   168                                     jint touch_device_id_in, jint pointer_finger_id_in,
   169                                     jint action, jfloat x, jfloat y, jfloat p)
   170 {
   171     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   172 }
   173 
   174 // Accelerometer
   175 void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   176                                     JNIEnv* env, jclass jcls,
   177                                     jfloat x, jfloat y, jfloat z)
   178 {
   179     fLastAccelerometer[0] = x;
   180     fLastAccelerometer[1] = y;
   181     fLastAccelerometer[2] = z;
   182     bHasNewData = true;
   183 }
   184 
   185 // Low memory
   186 void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
   187                                     JNIEnv* env, jclass cls)
   188 {
   189     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   190 }
   191 
   192 // Quit
   193 void Java_org_libsdl_app_SDLActivity_nativeQuit(
   194                                     JNIEnv* env, jclass cls)
   195 {
   196     // Inject a SDL_QUIT event
   197     SDL_SendQuit();
   198     SDL_SendAppEvent(SDL_APP_TERMINATING);
   199 }
   200 
   201 // Pause
   202 void Java_org_libsdl_app_SDLActivity_nativePause(
   203                                     JNIEnv* env, jclass cls)
   204 {
   205     if (Android_Window) {
   206         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   207         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   208         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   209         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   210     }
   211 
   212     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
   213     SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   214     SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   215 }
   216 
   217 // Resume
   218 void Java_org_libsdl_app_SDLActivity_nativeResume(
   219                                     JNIEnv* env, jclass cls)
   220 {
   221     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
   222     SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   223     SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   224 
   225     if (Android_Window) {
   226         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   227          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   228          * and this function will be called from the Java thread instead.
   229          */
   230         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   231         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   232         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   233     }
   234 }
   235 
   236 void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   237                                     JNIEnv* env, jclass cls)
   238 {
   239     /* This is the audio thread, with a different environment */
   240     Android_JNI_SetupThread();
   241 
   242     Android_RunAudioThread();
   243 }
   244 
   245 void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
   246                                     JNIEnv* env, jclass cls,
   247                                     jstring text, jint newCursorPosition)
   248 {
   249     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   250 
   251     SDL_SendKeyboardText(utftext);
   252 
   253     (*env)->ReleaseStringUTFChars(env, text, utftext);
   254 }
   255 
   256 void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
   257                                     JNIEnv* env, jclass cls,
   258                                     jstring text, jint newCursorPosition)
   259 {
   260     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   261 
   262     SDL_SendEditingText(utftext, 0, 0);
   263 
   264     (*env)->ReleaseStringUTFChars(env, text, utftext);
   265 }
   266 
   267 
   268 
   269 /*******************************************************************************
   270              Functions called by SDL into Java
   271 *******************************************************************************/
   272 
   273 static int s_active = 0;
   274 struct LocalReferenceHolder
   275 {
   276     JNIEnv *m_env;
   277     const char *m_func;
   278 };
   279 
   280 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
   281 {
   282     struct LocalReferenceHolder refholder;
   283     refholder.m_env = NULL;
   284     refholder.m_func = func;
   285 #ifdef DEBUG_JNI
   286     SDL_Log("Entering function %s", func);
   287 #endif
   288     return refholder;
   289 }
   290 
   291 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
   292 {
   293     const int capacity = 16;
   294     if ((*env)->PushLocalFrame(env, capacity) < 0) {
   295         SDL_SetError("Failed to allocate enough JVM local references");
   296         return SDL_FALSE;
   297     }
   298     ++s_active;
   299     refholder->m_env = env;
   300     return SDL_TRUE;
   301 }
   302 
   303 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
   304 {
   305 #ifdef DEBUG_JNI
   306     SDL_Log("Leaving function %s", refholder->m_func);
   307 #endif
   308     if (refholder->m_env) {
   309         JNIEnv* env = refholder->m_env;
   310         (*env)->PopLocalFrame(env, NULL);
   311         --s_active;
   312     }
   313 }
   314 
   315 static SDL_bool LocalReferenceHolder_IsActive()
   316 {
   317     return s_active > 0;    
   318 }
   319 
   320 
   321 SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
   322                                 int red, int green, int blue, int alpha,
   323                                 int buffer, int depth, int stencil,
   324                                 int buffers, int samples)
   325 {
   326     JNIEnv *env = Android_JNI_GetEnv();
   327 
   328     jint attribs[] = {
   329         EGL_RED_SIZE, red,
   330         EGL_GREEN_SIZE, green,
   331         EGL_BLUE_SIZE, blue,
   332         EGL_ALPHA_SIZE, alpha,
   333         EGL_BUFFER_SIZE, buffer,
   334         EGL_DEPTH_SIZE, depth,
   335         EGL_STENCIL_SIZE, stencil,
   336         EGL_SAMPLE_BUFFERS, buffers,
   337         EGL_SAMPLES, samples,
   338         EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
   339         EGL_NONE
   340     };
   341     int len = SDL_arraysize(attribs);
   342 
   343     jintArray array;
   344 
   345     array = (*env)->NewIntArray(env, len);
   346     (*env)->SetIntArrayRegion(env, array, 0, len, attribs);
   347 
   348     jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
   349 
   350     (*env)->DeleteLocalRef(env, array);
   351 
   352     return success ? SDL_TRUE : SDL_FALSE;
   353 }
   354 
   355 void Android_JNI_SwapWindow()
   356 {
   357     JNIEnv *mEnv = Android_JNI_GetEnv();
   358     (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers);
   359 }
   360 
   361 void Android_JNI_SetActivityTitle(const char *title)
   362 {
   363     jmethodID mid;
   364     JNIEnv *mEnv = Android_JNI_GetEnv();
   365     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
   366     if (mid) {
   367         jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
   368         (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle);
   369         (*mEnv)->DeleteLocalRef(mEnv, jtitle);
   370     }
   371 }
   372 
   373 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   374 {
   375     int i;
   376     SDL_bool retval = SDL_FALSE;
   377 
   378     if (bHasNewData) {
   379         for (i = 0; i < 3; ++i) {
   380             values[i] = fLastAccelerometer[i];
   381         }
   382         bHasNewData = false;
   383         retval = SDL_TRUE;
   384     }
   385 
   386     return retval;
   387 }
   388 
   389 static void Android_JNI_ThreadDestroyed(void* value) {
   390     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   391     JNIEnv *env = (JNIEnv*) value;
   392     if (env != NULL) {
   393         (*mJavaVM)->DetachCurrentThread(mJavaVM);
   394         pthread_setspecific(mThreadKey, NULL);
   395     }
   396 }
   397 
   398 JNIEnv* Android_JNI_GetEnv(void) {
   399     /* From http://developer.android.com/guide/practices/jni.html
   400      * All threads are Linux threads, scheduled by the kernel.
   401      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   402      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   403      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   404      * and cannot make JNI calls.
   405      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   406      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   407      * is a no-op.
   408      * Note: You can call this function any number of times for the same thread, there's no harm in it
   409      */
   410 
   411     JNIEnv *env;
   412     int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   413     if(status < 0) {
   414         LOGE("failed to attach current thread");
   415         return 0;
   416     }
   417 
   418     return env;
   419 }
   420 
   421 int Android_JNI_SetupThread(void) {
   422     /* From http://developer.android.com/guide/practices/jni.html
   423      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   424      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   425      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   426      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   427      * Note: The destructor is not called unless the stored value is != NULL
   428      * Note: You can call this function any number of times for the same thread, there's no harm in it
   429      *       (except for some lost CPU cycles)
   430      */
   431     JNIEnv *env = Android_JNI_GetEnv();
   432     pthread_setspecific(mThreadKey, (void*) env);
   433     return 1;
   434 }
   435 
   436 //
   437 // Audio support
   438 //
   439 static jboolean audioBuffer16Bit = JNI_FALSE;
   440 static jboolean audioBufferStereo = JNI_FALSE;
   441 static jobject audioBuffer = NULL;
   442 static void* audioBufferPinned = NULL;
   443 
   444 int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   445 {
   446     int audioBufferFrames;
   447 
   448     JNIEnv *env = Android_JNI_GetEnv();
   449 
   450     if (!env) {
   451         LOGE("callback_handler: failed to attach current thread");
   452     }
   453     Android_JNI_SetupThread();
   454 
   455     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   456     audioBuffer16Bit = is16Bit;
   457     audioBufferStereo = channelCount > 1;
   458 
   459     if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
   460         /* Error during audio initialization */
   461         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
   462         return 0;
   463     }
   464 
   465     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   466      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   467 
   468     if (is16Bit) {
   469         jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   470         if (audioBufferLocal) {
   471             audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   472             (*env)->DeleteLocalRef(env, audioBufferLocal);
   473         }
   474     }
   475     else {
   476         jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   477         if (audioBufferLocal) {
   478             audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   479             (*env)->DeleteLocalRef(env, audioBufferLocal);
   480         }
   481     }
   482 
   483     if (audioBuffer == NULL) {
   484         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   485         return 0;
   486     }
   487 
   488     jboolean isCopy = JNI_FALSE;
   489     if (audioBuffer16Bit) {
   490         audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   491         audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
   492     } else {
   493         audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   494         audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
   495     }
   496     if (audioBufferStereo) {
   497         audioBufferFrames /= 2;
   498     }
   499 
   500     return audioBufferFrames;
   501 }
   502 
   503 void * Android_JNI_GetAudioBuffer()
   504 {
   505     return audioBufferPinned;
   506 }
   507 
   508 void Android_JNI_WriteAudioBuffer()
   509 {
   510     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   511 
   512     if (audioBuffer16Bit) {
   513         (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   514         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   515     } else {
   516         (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   517         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   518     }
   519 
   520     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   521 }
   522 
   523 void Android_JNI_CloseAudioDevice()
   524 {
   525     JNIEnv *env = Android_JNI_GetEnv();
   526 
   527     (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
   528 
   529     if (audioBuffer) {
   530         (*env)->DeleteGlobalRef(env, audioBuffer);
   531         audioBuffer = NULL;
   532         audioBufferPinned = NULL;
   533     }
   534 }
   535 
   536 // Test for an exception and call SDL_SetError with its detail if one occurs
   537 // If the parameter silent is truthy then SDL_SetError() will not be called.
   538 static bool Android_JNI_ExceptionOccurred(bool silent)
   539 {
   540     SDL_assert(LocalReferenceHolder_IsActive());
   541     JNIEnv *mEnv = Android_JNI_GetEnv();
   542 
   543     jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv);
   544     if (exception != NULL) {
   545         jmethodID mid;
   546 
   547         // Until this happens most JNI operations have undefined behaviour
   548         (*mEnv)->ExceptionClear(mEnv);
   549 
   550         if (!silent) {
   551             jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
   552             jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
   553 
   554             mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
   555             jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
   556             const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
   557 
   558             mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
   559             jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
   560 
   561             if (exceptionMessage != NULL) {
   562                 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
   563                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   564                 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
   565             } else {
   566                 SDL_SetError("%s", exceptionNameUTF8);
   567             }
   568 
   569             (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
   570         }
   571 
   572         return true;
   573     }
   574 
   575     return false;
   576 }
   577 
   578 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
   579 {
   580     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   581 
   582     int result = 0;
   583 
   584     jmethodID mid;
   585     jobject context;
   586     jobject assetManager;
   587     jobject inputStream;
   588     jclass channels;
   589     jobject readableByteChannel;
   590     jstring fileNameJString;
   591     jobject fd;
   592     jclass fdCls;
   593     jfieldID descriptor;
   594 
   595     JNIEnv *mEnv = Android_JNI_GetEnv();
   596     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   597         goto failure;
   598     }
   599 
   600     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   601     ctx->hidden.androidio.position = 0;
   602 
   603     // context = SDLActivity.getContext();
   604     mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   605             "getContext","()Landroid/content/Context;");
   606     context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
   607 
   608 
   609     // assetManager = context.getAssets();
   610     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
   611             "getAssets", "()Landroid/content/res/AssetManager;");
   612     assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
   613 
   614     /* First let's try opening the file to obtain an AssetFileDescriptor.
   615     * This method reads the files directly from the APKs using standard *nix calls
   616     */
   617     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   618     inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
   619     if (Android_JNI_ExceptionOccurred(true)) {
   620         goto fallback;
   621     }
   622 
   623     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
   624     ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   625     if (Android_JNI_ExceptionOccurred(true)) {
   626         goto fallback;
   627     }
   628 
   629     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
   630     ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   631     if (Android_JNI_ExceptionOccurred(true)) {
   632         goto fallback;
   633     }
   634 
   635     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   636     fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
   637     fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
   638     descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
   639     ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
   640     ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   641 
   642     // Seek to the correct offset in the file.
   643     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   644 
   645     if (false) {
   646 fallback:
   647         // Disabled log message because of spam on the Nexus 7
   648         //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   649 
   650         /* Try the old method using InputStream */
   651         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   652 
   653         // inputStream = assetManager.open(<filename>);
   654         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
   655                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   656         inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   657         if (Android_JNI_ExceptionOccurred(false)) {
   658             goto failure;
   659         }
   660 
   661         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   662 
   663         // Despite all the visible documentation on [Asset]InputStream claiming
   664         // that the .available() method is not guaranteed to return the entire file
   665         // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   666         // android/apis/content/ReadAsset.java imply that Android's
   667         // AssetInputStream.available() /will/ always return the total file size
   668 
   669         // size = inputStream.available();
   670         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   671                 "available", "()I");
   672         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
   673         if (Android_JNI_ExceptionOccurred(false)) {
   674             goto failure;
   675         }
   676 
   677         // readableByteChannel = Channels.newChannel(inputStream);
   678         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
   679         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
   680                 "newChannel",
   681                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   682         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
   683                 mEnv, channels, mid, inputStream);
   684         if (Android_JNI_ExceptionOccurred(false)) {
   685             goto failure;
   686         }
   687 
   688         ctx->hidden.androidio.readableByteChannelRef =
   689             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
   690 
   691         // Store .read id for reading purposes
   692         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
   693                 "read", "(Ljava/nio/ByteBuffer;)I");
   694         ctx->hidden.androidio.readMethod = mid;
   695     }
   696 
   697     if (false) {
   698 failure:
   699         result = -1;
   700 
   701         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   702 
   703         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   704             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   705         }
   706 
   707         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   708             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   709         }
   710 
   711         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   712             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   713         }
   714 
   715     }
   716     
   717     LocalReferenceHolder_Cleanup(&refs);
   718     return result;
   719 }
   720 
   721 int Android_JNI_FileOpen(SDL_RWops* ctx,
   722         const char* fileName, const char* mode)
   723 {
   724     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   725     JNIEnv *mEnv = Android_JNI_GetEnv();
   726     int retval;
   727 
   728     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   729         LocalReferenceHolder_Cleanup(&refs);        
   730         return -1;
   731     }
   732 
   733     if (!ctx) {
   734         LocalReferenceHolder_Cleanup(&refs);
   735         return -1;
   736     }
   737 
   738     jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
   739     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
   740     ctx->hidden.androidio.inputStreamRef = NULL;
   741     ctx->hidden.androidio.readableByteChannelRef = NULL;
   742     ctx->hidden.androidio.readMethod = NULL;
   743     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   744 
   745     retval = Internal_Android_JNI_FileOpen(ctx);
   746     LocalReferenceHolder_Cleanup(&refs);
   747     return retval;
   748 }
   749 
   750 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   751         size_t size, size_t maxnum)
   752 {
   753     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   754 
   755     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   756         size_t bytesMax = size * maxnum;
   757         if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   758             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   759         }
   760         size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   761         if (result > 0) {
   762             ctx->hidden.androidio.position += result;
   763             LocalReferenceHolder_Cleanup(&refs);
   764             return result / size;
   765         }
   766         LocalReferenceHolder_Cleanup(&refs);
   767         return 0;
   768     } else {
   769         jlong bytesRemaining = (jlong) (size * maxnum);
   770         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   771         int bytesRead = 0;
   772 
   773         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   774         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   775 
   776         JNIEnv *mEnv = Android_JNI_GetEnv();
   777         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   778             LocalReferenceHolder_Cleanup(&refs);            
   779             return 0;
   780         }
   781 
   782         jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   783         jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   784         jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
   785 
   786         while (bytesRemaining > 0) {
   787             // result = readableByteChannel.read(...);
   788             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
   789 
   790             if (Android_JNI_ExceptionOccurred(false)) {
   791                 LocalReferenceHolder_Cleanup(&refs);            
   792                 return 0;
   793             }
   794 
   795             if (result < 0) {
   796                 break;
   797             }
   798 
   799             bytesRemaining -= result;
   800             bytesRead += result;
   801             ctx->hidden.androidio.position += result;
   802         }
   803         LocalReferenceHolder_Cleanup(&refs);                    
   804         return bytesRead / size;
   805     }
   806 }
   807 
   808 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   809         size_t size, size_t num)
   810 {
   811     SDL_SetError("Cannot write to Android package filesystem");
   812     return 0;
   813 }
   814 
   815 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   816 {
   817     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   818 
   819     int result = 0;
   820     JNIEnv *mEnv = Android_JNI_GetEnv();
   821 
   822     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   823         LocalReferenceHolder_Cleanup(&refs);
   824         return SDL_SetError("Failed to allocate enough JVM local references");
   825     }
   826 
   827     if (ctx) {
   828         if (release) {
   829             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   830         }
   831 
   832         if (ctx->hidden.androidio.assetFileDescriptorRef) {
   833             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   834             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   835                     "close", "()V");
   836             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   837             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   838             if (Android_JNI_ExceptionOccurred(false)) {
   839                 result = -1;
   840             }
   841         }
   842         else {
   843             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   844 
   845             // inputStream.close();
   846             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   847                     "close", "()V");
   848             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   849             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   850             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   851             if (Android_JNI_ExceptionOccurred(false)) {
   852                 result = -1;
   853             }
   854         }
   855 
   856         if (release) {
   857             SDL_FreeRW(ctx);
   858         }
   859     }
   860 
   861     LocalReferenceHolder_Cleanup(&refs);
   862     return result;
   863 }
   864 
   865 
   866 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   867 {
   868     return ctx->hidden.androidio.size;
   869 }
   870 
   871 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   872 {
   873     if (ctx->hidden.androidio.assetFileDescriptorRef) {
   874         switch (whence) {
   875             case RW_SEEK_SET:
   876                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   877                 offset += ctx->hidden.androidio.offset;
   878                 break;
   879             case RW_SEEK_CUR:
   880                 offset += ctx->hidden.androidio.position;
   881                 if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   882                 offset += ctx->hidden.androidio.offset;
   883                 break;
   884             case RW_SEEK_END:
   885                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   886                 break;
   887             default:
   888                 return SDL_SetError("Unknown value for 'whence'");
   889         }
   890         whence = SEEK_SET;
   891 
   892         off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   893         if (ret == -1) return -1;
   894         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   895     } else {
   896         Sint64 newPosition;
   897 
   898         switch (whence) {
   899             case RW_SEEK_SET:
   900                 newPosition = offset;
   901                 break;
   902             case RW_SEEK_CUR:
   903                 newPosition = ctx->hidden.androidio.position + offset;
   904                 break;
   905             case RW_SEEK_END:
   906                 newPosition = ctx->hidden.androidio.size + offset;
   907                 break;
   908             default:
   909                 return SDL_SetError("Unknown value for 'whence'");
   910         }
   911 
   912         /* Validate the new position */
   913         if (newPosition < 0) {
   914             return SDL_Error(SDL_EFSEEK);
   915         }
   916         if (newPosition > ctx->hidden.androidio.size) {
   917             newPosition = ctx->hidden.androidio.size;
   918         }
   919 
   920         Sint64 movement = newPosition - ctx->hidden.androidio.position;
   921         if (movement > 0) {
   922             unsigned char buffer[4096];
   923 
   924             // The easy case where we're seeking forwards
   925             while (movement > 0) {
   926                 Sint64 amount = sizeof (buffer);
   927                 if (amount > movement) {
   928                     amount = movement;
   929                 }
   930                 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   931                 if (result <= 0) {
   932                     // Failed to read/skip the required amount, so fail
   933                     return -1;
   934                 }
   935 
   936                 movement -= result;
   937             }
   938 
   939         } else if (movement < 0) {
   940             // We can't seek backwards so we have to reopen the file and seek
   941             // forwards which obviously isn't very efficient
   942             Internal_Android_JNI_FileClose(ctx, false);
   943             Internal_Android_JNI_FileOpen(ctx);
   944             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   945         }
   946     }
   947 
   948     return ctx->hidden.androidio.position;
   949 
   950 }
   951 
   952 int Android_JNI_FileClose(SDL_RWops* ctx)
   953 {
   954     return Internal_Android_JNI_FileClose(ctx, true);
   955 }
   956 
   957 // returns a new global reference which needs to be released later
   958 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   959 {
   960     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   961     JNIEnv* env = Android_JNI_GetEnv();
   962     jobject retval = NULL;
   963 
   964     if (!LocalReferenceHolder_Init(&refs, env)) {
   965         LocalReferenceHolder_Cleanup(&refs);
   966         return NULL;
   967     }
   968 
   969     jstring service = (*env)->NewStringUTF(env, name);
   970 
   971     jmethodID mid;
   972 
   973     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
   974     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
   975 
   976     mid = (*env)->GetMethodID(env, mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   977     jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
   978 
   979     (*env)->DeleteLocalRef(env, service);
   980 
   981     retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
   982     LocalReferenceHolder_Cleanup(&refs);
   983     return retval;
   984 }
   985 
   986 #define SETUP_CLIPBOARD(error) \
   987     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
   988     JNIEnv* env = Android_JNI_GetEnv(); \
   989     if (!LocalReferenceHolder_Init(&refs, env)) { \
   990         LocalReferenceHolder_Cleanup(&refs); \
   991         return error; \
   992     } \
   993     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   994     if (!clipboard) { \
   995         LocalReferenceHolder_Cleanup(&refs); \
   996         return error; \
   997     }
   998 
   999 #define CLEANUP_CLIPBOARD() \
  1000     LocalReferenceHolder_Cleanup(&refs);
  1001 
  1002 int Android_JNI_SetClipboardText(const char* text)
  1003 {
  1004     SETUP_CLIPBOARD(-1)
  1005 
  1006     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
  1007     jstring string = (*env)->NewStringUTF(env, text);
  1008     (*env)->CallVoidMethod(env, clipboard, mid, string);
  1009     (*env)->DeleteGlobalRef(env, clipboard);
  1010     (*env)->DeleteLocalRef(env, string);
  1011 
  1012     CLEANUP_CLIPBOARD();
  1013 
  1014     return 0;
  1015 }
  1016 
  1017 char* Android_JNI_GetClipboardText()
  1018 {
  1019     SETUP_CLIPBOARD(SDL_strdup(""))
  1020 
  1021     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
  1022     jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
  1023     (*env)->DeleteGlobalRef(env, clipboard);
  1024     if (sequence) {
  1025         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
  1026         jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
  1027         const char* utf = (*env)->GetStringUTFChars(env, string, 0);
  1028         if (utf) {
  1029             char* text = SDL_strdup(utf);
  1030             (*env)->ReleaseStringUTFChars(env, string, utf);
  1031 
  1032             CLEANUP_CLIPBOARD();
  1033 
  1034             return text;
  1035         }
  1036     }
  1037 
  1038     CLEANUP_CLIPBOARD();    
  1039 
  1040     return SDL_strdup("");
  1041 }
  1042 
  1043 SDL_bool Android_JNI_HasClipboardText()
  1044 {
  1045     SETUP_CLIPBOARD(SDL_FALSE)
  1046 
  1047     jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
  1048     jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
  1049     (*env)->DeleteGlobalRef(env, clipboard);
  1050 
  1051     CLEANUP_CLIPBOARD();
  1052     
  1053     return has ? SDL_TRUE : SDL_FALSE;
  1054 }
  1055 
  1056 
  1057 // returns 0 on success or -1 on error (others undefined then)
  1058 // returns truthy or falsy value in plugged, charged and battery
  1059 // returns the value in seconds and percent or -1 if not available
  1060 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  1061 {
  1062     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1063     JNIEnv* env = Android_JNI_GetEnv();
  1064     if (!LocalReferenceHolder_Init(&refs, env)) {
  1065         LocalReferenceHolder_Cleanup(&refs);
  1066         return -1;
  1067     }
  1068 
  1069     jmethodID mid;
  1070 
  1071     mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  1072     jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1073 
  1074     jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  1075 
  1076     jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
  1077 
  1078     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  1079     jobject filter = (*env)->NewObject(env, cls, mid, action);
  1080 
  1081     (*env)->DeleteLocalRef(env, action);
  1082 
  1083     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  1084     jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  1085 
  1086     (*env)->DeleteLocalRef(env, filter);
  1087 
  1088     cls = (*env)->GetObjectClass(env, intent);
  1089 
  1090     jstring iname;
  1091     jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  1092 
  1093 #define GET_INT_EXTRA(var, key) \
  1094     iname = (*env)->NewStringUTF(env, key); \
  1095     int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  1096     (*env)->DeleteLocalRef(env, iname);
  1097 
  1098     jstring bname;
  1099     jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  1100 
  1101 #define GET_BOOL_EXTRA(var, key) \
  1102     bname = (*env)->NewStringUTF(env, key); \
  1103     int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  1104     (*env)->DeleteLocalRef(env, bname);
  1105 
  1106     if (plugged) {
  1107         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  1108         if (plug == -1) {
  1109             LocalReferenceHolder_Cleanup(&refs);
  1110             return -1;
  1111         }
  1112         // 1 == BatteryManager.BATTERY_PLUGGED_AC
  1113         // 2 == BatteryManager.BATTERY_PLUGGED_USB
  1114         *plugged = (0 < plug) ? 1 : 0;
  1115     }
  1116 
  1117     if (charged) {
  1118         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  1119         if (status == -1) {
  1120             LocalReferenceHolder_Cleanup(&refs);
  1121             return -1;
  1122         }
  1123         // 5 == BatteryManager.BATTERY_STATUS_FULL
  1124         *charged = (status == 5) ? 1 : 0;
  1125     }
  1126 
  1127     if (battery) {
  1128         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  1129         *battery = present ? 1 : 0;
  1130     }
  1131 
  1132     if (seconds) {
  1133         *seconds = -1; // not possible
  1134     }
  1135 
  1136     if (percent) {
  1137         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  1138         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  1139         if ((level == -1) || (scale == -1)) {
  1140             LocalReferenceHolder_Cleanup(&refs);
  1141             return -1;
  1142         }
  1143         *percent = level * 100 / scale;
  1144     }
  1145 
  1146     (*env)->DeleteLocalRef(env, intent);
  1147 
  1148     LocalReferenceHolder_Cleanup(&refs);
  1149     return 0;
  1150 }
  1151 
  1152 // sends message to be handled on the UI event dispatch thread
  1153 int Android_JNI_SendMessage(int command, int param)
  1154 {
  1155     JNIEnv *env = Android_JNI_GetEnv();
  1156     if (!env) {
  1157         return -1;
  1158     }
  1159     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
  1160     if (!mid) {
  1161         return -1;
  1162     }
  1163     jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
  1164     return success ? 0 : -1;
  1165 }
  1166 
  1167 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  1168 {
  1169     JNIEnv *env = Android_JNI_GetEnv();
  1170     if (!env) {
  1171         return;
  1172     }
  1173 
  1174     jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
  1175     if (!mid) {
  1176         return;
  1177     }
  1178     (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
  1179                                inputRect->x,
  1180                                inputRect->y,
  1181                                inputRect->w,
  1182                                inputRect->h );
  1183 }
  1184 
  1185 void Android_JNI_HideTextInput()
  1186 {
  1187     // has to match Activity constant
  1188     const int COMMAND_TEXTEDIT_HIDE = 3;
  1189     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  1190 }
  1191 
  1192 //////////////////////////////////////////////////////////////////////////////
  1193 //
  1194 // Functions exposed to SDL applications in SDL_system.h
  1195 //
  1196 
  1197 void *SDL_AndroidGetJNIEnv()
  1198 {
  1199     return Android_JNI_GetEnv();
  1200 }
  1201 
  1202 
  1203 
  1204 void *SDL_AndroidGetActivity()
  1205 {
  1206     /* See SDL_system.h for caveats on using this function. */
  1207 
  1208     jmethodID mid;
  1209 
  1210     JNIEnv *env = Android_JNI_GetEnv();
  1211     if (!env) {
  1212         return NULL;
  1213     }
  1214 
  1215     // return SDLActivity.getContext();
  1216     mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1217             "getContext","()Landroid/content/Context;");
  1218     return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1219 }
  1220 
  1221 const char * SDL_AndroidGetInternalStoragePath()
  1222 {
  1223     static char *s_AndroidInternalFilesPath = NULL;
  1224 
  1225     if (!s_AndroidInternalFilesPath) {
  1226         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1227         jmethodID mid;
  1228         jobject context;
  1229         jobject fileObject;
  1230         jstring pathString;
  1231         const char *path;
  1232 
  1233         JNIEnv *env = Android_JNI_GetEnv();
  1234         if (!LocalReferenceHolder_Init(&refs, env)) {
  1235             LocalReferenceHolder_Cleanup(&refs);
  1236             return NULL;
  1237         }
  1238 
  1239         // context = SDLActivity.getContext();
  1240         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1241                 "getContext","()Landroid/content/Context;");
  1242         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1243 
  1244         // fileObj = context.getFilesDir();
  1245         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1246                 "getFilesDir", "()Ljava/io/File;");
  1247         fileObject = (*env)->CallObjectMethod(env, context, mid);
  1248         if (!fileObject) {
  1249             SDL_SetError("Couldn't get internal directory");
  1250             LocalReferenceHolder_Cleanup(&refs);
  1251             return NULL;
  1252         }
  1253 
  1254         // path = fileObject.getAbsolutePath();
  1255         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1256                 "getAbsolutePath", "()Ljava/lang/String;");
  1257         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1258 
  1259         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1260         s_AndroidInternalFilesPath = SDL_strdup(path);
  1261         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1262 
  1263         LocalReferenceHolder_Cleanup(&refs);
  1264     }
  1265     return s_AndroidInternalFilesPath;
  1266 }
  1267 
  1268 int SDL_AndroidGetExternalStorageState()
  1269 {
  1270     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1271     jmethodID mid;
  1272     jclass cls;
  1273     jstring stateString;
  1274     const char *state;
  1275     int stateFlags;
  1276 
  1277     JNIEnv *env = Android_JNI_GetEnv();
  1278     if (!LocalReferenceHolder_Init(&refs, env)) {
  1279         LocalReferenceHolder_Cleanup(&refs);
  1280         return 0;
  1281     }
  1282 
  1283     cls = (*env)->FindClass(env, "android/os/Environment");
  1284     mid = (*env)->GetStaticMethodID(env, cls,
  1285             "getExternalStorageState", "()Ljava/lang/String;");
  1286     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  1287 
  1288     state = (*env)->GetStringUTFChars(env, stateString, NULL);
  1289 
  1290     // Print an info message so people debugging know the storage state
  1291     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  1292 
  1293     if (SDL_strcmp(state, "mounted") == 0) {
  1294         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  1295                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  1296     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  1297         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  1298     } else {
  1299         stateFlags = 0;
  1300     }
  1301     (*env)->ReleaseStringUTFChars(env, stateString, state);
  1302 
  1303     LocalReferenceHolder_Cleanup(&refs);
  1304     return stateFlags;
  1305 }
  1306 
  1307 const char * SDL_AndroidGetExternalStoragePath()
  1308 {
  1309     static char *s_AndroidExternalFilesPath = NULL;
  1310 
  1311     if (!s_AndroidExternalFilesPath) {
  1312         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  1313         jmethodID mid;
  1314         jobject context;
  1315         jobject fileObject;
  1316         jstring pathString;
  1317         const char *path;
  1318 
  1319         JNIEnv *env = Android_JNI_GetEnv();
  1320         if (!LocalReferenceHolder_Init(&refs, env)) {
  1321             LocalReferenceHolder_Cleanup(&refs);
  1322             return NULL;
  1323         }
  1324 
  1325         // context = SDLActivity.getContext();
  1326         mid = (*env)->GetStaticMethodID(env, mActivityClass,
  1327                 "getContext","()Landroid/content/Context;");
  1328         context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  1329 
  1330         // fileObj = context.getExternalFilesDir();
  1331         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  1332                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  1333         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  1334         if (!fileObject) {
  1335             SDL_SetError("Couldn't get external directory");
  1336             LocalReferenceHolder_Cleanup(&refs);
  1337             return NULL;
  1338         }
  1339 
  1340         // path = fileObject.getAbsolutePath();
  1341         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  1342                 "getAbsolutePath", "()Ljava/lang/String;");
  1343         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  1344 
  1345         path = (*env)->GetStringUTFChars(env, pathString, NULL);
  1346         s_AndroidExternalFilesPath = SDL_strdup(path);
  1347         (*env)->ReleaseStringUTFChars(env, pathString, path);
  1348 
  1349         LocalReferenceHolder_Cleanup(&refs);
  1350     }
  1351     return s_AndroidExternalFilesPath;
  1352 }
  1353 
  1354 #endif /* __ANDROID__ */
  1355 
  1356 /* vi: set ts=4 sw=4 expandtab: */