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