src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Wed, 26 Sep 2012 20:14:37 -0700
changeset 6464 ab55284b389f
parent 6448 64a6297a8b93
child 6555 f2c03c06d987
permissions -rwxr-xr-x
Fixed bug 1573 - SDL does not support system clipboard on Android.

Philipp Wiesemann 2012-08-18 14:09:47 PDT

there is currently no way in SDL to interact with the system clipboard on
Android.

I attached a patch which tries to implement the three clipboard functions for
Android. It does not add the CLIPBOARDUPDATE event because this seems to
require Android API 11 or polling.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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 
    25 #ifdef __ANDROID__
    26 
    27 #include "SDL_android.h"
    28 
    29 extern "C" {
    30 #include "../../events/SDL_events_c.h"
    31 #include "../../video/android/SDL_androidkeyboard.h"
    32 #include "../../video/android/SDL_androidtouch.h"
    33 #include "../../video/android/SDL_androidvideo.h"
    34 
    35 #include <android/log.h>
    36 #include <pthread.h>
    37 #define LOG_TAG "SDL_android"
    38 //#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    39 //#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    40 #define LOGI(...) do {} while (false)
    41 #define LOGE(...) do {} while (false)
    42 
    43 
    44 /* Implemented in audio/android/SDL_androidaudio.c */
    45 extern void Android_RunAudioThread();
    46 } // C
    47 
    48 /*******************************************************************************
    49  This file links the Java side of Android with libsdl
    50 *******************************************************************************/
    51 #include <jni.h>
    52 #include <android/log.h>
    53 
    54 
    55 /*******************************************************************************
    56                                Globals
    57 *******************************************************************************/
    58 static pthread_key_t mThreadKey;
    59 static JavaVM* mJavaVM;
    60 
    61 // Main activity
    62 static jclass mActivityClass;
    63 
    64 // method signatures
    65 static jmethodID midCreateGLContext;
    66 static jmethodID midFlipBuffers;
    67 static jmethodID midAudioInit;
    68 static jmethodID midAudioWriteShortBuffer;
    69 static jmethodID midAudioWriteByteBuffer;
    70 static jmethodID midAudioQuit;
    71 
    72 // Accelerometer data storage
    73 static float fLastAccelerometer[3];
    74 static bool bHasNewData;
    75 
    76 /*******************************************************************************
    77                  Functions called by JNI
    78 *******************************************************************************/
    79 
    80 // Library init
    81 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    82 {
    83     JNIEnv *env;
    84     mJavaVM = vm;
    85     LOGI("JNI_OnLoad called");
    86     if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    87         LOGE("Failed to get the environment using GetEnv()");
    88         return -1;
    89     }
    90     /*
    91      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
    92      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
    93      */
    94     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
    95         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
    96     }
    97     else {
    98         Android_JNI_SetupThread();
    99     }
   100 
   101     return JNI_VERSION_1_4;
   102 }
   103 
   104 // Called before SDL_main() to initialize JNI bindings
   105 extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   106 {
   107     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   108 
   109     Android_JNI_SetupThread();
   110 
   111     mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
   112 
   113     midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   114                                 "createGLContext","(II)Z");
   115     midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   116                                 "flipBuffers","()V");
   117     midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
   118                                 "audioInit", "(IZZI)Ljava/lang/Object;");
   119     midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   120                                 "audioWriteShortBuffer", "([S)V");
   121     midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   122                                 "audioWriteByteBuffer", "([B)V");
   123     midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   124                                 "audioQuit", "()V");
   125 
   126     bHasNewData = false;
   127 
   128     if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   129        !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   130         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   131     }
   132     __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   133 }
   134 
   135 // Resize
   136 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   137                                     JNIEnv* env, jclass jcls,
   138                                     jint width, jint height, jint format)
   139 {
   140     Android_SetScreenResolution(width, height, format);
   141 }
   142 
   143 // Keydown
   144 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   145                                     JNIEnv* env, jclass jcls, jint keycode)
   146 {
   147     Android_OnKeyDown(keycode);
   148 }
   149 
   150 // Keyup
   151 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   152                                     JNIEnv* env, jclass jcls, jint keycode)
   153 {
   154     Android_OnKeyUp(keycode);
   155 }
   156 
   157 // Touch
   158 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   159                                     JNIEnv* env, jclass jcls,
   160                                     jint touch_device_id_in, jint pointer_finger_id_in,
   161                                     jint action, jfloat x, jfloat y, jfloat p)
   162 {
   163     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   164 }
   165 
   166 // Accelerometer
   167 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   168                                     JNIEnv* env, jclass jcls,
   169                                     jfloat x, jfloat y, jfloat z)
   170 {
   171     fLastAccelerometer[0] = x;
   172     fLastAccelerometer[1] = y;
   173     fLastAccelerometer[2] = z;
   174     bHasNewData = true;
   175 }
   176 
   177 // Quit
   178 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   179                                     JNIEnv* env, jclass cls)
   180 {    
   181     // Inject a SDL_QUIT event
   182     SDL_SendQuit();
   183 }
   184 
   185 // Pause
   186 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   187                                     JNIEnv* env, jclass cls)
   188 {
   189     if (Android_Window) {
   190         /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   191         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   192         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   193         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   194     }
   195 }
   196 
   197 // Resume
   198 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   199                                     JNIEnv* env, jclass cls)
   200 {
   201     if (Android_Window) {
   202         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   203          * We can't restore the GL Context here because it needs to be done on the SDL main thread
   204          * and this function will be called from the Java thread instead.
   205          */
   206         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   207         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   208         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   209     }
   210 }
   211 
   212 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   213                                     JNIEnv* env, jclass cls)
   214 {
   215     /* This is the audio thread, with a different environment */
   216     Android_JNI_SetupThread();
   217 
   218     Android_RunAudioThread();
   219 }
   220 
   221 
   222 /*******************************************************************************
   223              Functions called by SDL into Java
   224 *******************************************************************************/
   225 
   226 class LocalReferenceHolder
   227 {
   228 private:
   229     static int s_active;
   230 
   231 public:
   232     static bool IsActive() {
   233         return s_active > 0;
   234     }
   235 
   236 public:
   237     LocalReferenceHolder() : m_env(NULL) { }
   238     ~LocalReferenceHolder() {
   239         if (m_env) {
   240             m_env->PopLocalFrame(NULL);
   241             --s_active;
   242         }
   243     }
   244 
   245     bool init(JNIEnv *env, jint capacity = 16) {
   246         if (env->PushLocalFrame(capacity) < 0) {
   247             SDL_SetError("Failed to allocate enough JVM local references");
   248             return false;
   249         }
   250         ++s_active;
   251         m_env = env;
   252         return true;
   253     }
   254 
   255 protected:
   256     JNIEnv *m_env;
   257 };
   258 int LocalReferenceHolder::s_active;
   259 
   260 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
   261 {
   262     JNIEnv *mEnv = Android_JNI_GetEnv();
   263     if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
   264         return SDL_TRUE;
   265     } else {
   266         return SDL_FALSE;
   267     }
   268 }
   269 
   270 extern "C" void Android_JNI_SwapWindow()
   271 {
   272     JNIEnv *mEnv = Android_JNI_GetEnv();
   273     mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
   274 }
   275 
   276 extern "C" void Android_JNI_SetActivityTitle(const char *title)
   277 {
   278     jmethodID mid;
   279     JNIEnv *mEnv = Android_JNI_GetEnv();
   280     mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
   281     if (mid) {
   282         jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   283         mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
   284         mEnv->DeleteLocalRef(jtitle);
   285     }
   286 }
   287 
   288 extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   289 {
   290     int i;
   291     SDL_bool retval = SDL_FALSE;
   292 
   293     if (bHasNewData) {
   294         for (i = 0; i < 3; ++i) {
   295             values[i] = fLastAccelerometer[i];
   296         }
   297         bHasNewData = false;
   298         retval = SDL_TRUE;
   299     }
   300 
   301     return retval;
   302 }
   303 
   304 static void Android_JNI_ThreadDestroyed(void* value) {
   305     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   306     JNIEnv *env = (JNIEnv*) value;
   307     if (env != NULL) {
   308         mJavaVM->DetachCurrentThread();
   309         pthread_setspecific(mThreadKey, NULL);
   310     }
   311 }
   312 
   313 JNIEnv* Android_JNI_GetEnv(void) {
   314     /* From http://developer.android.com/guide/practices/jni.html
   315      * All threads are Linux threads, scheduled by the kernel.
   316      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   317      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   318      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   319      * and cannot make JNI calls.
   320      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   321      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   322      * is a no-op.
   323      * Note: You can call this function any number of times for the same thread, there's no harm in it
   324      */
   325 
   326     JNIEnv *env;
   327     int status = mJavaVM->AttachCurrentThread(&env, NULL);
   328     if(status < 0) {
   329         LOGE("failed to attach current thread");
   330         return 0;
   331     }
   332 
   333     return env;
   334 }
   335 
   336 int Android_JNI_SetupThread(void) {
   337     /* From http://developer.android.com/guide/practices/jni.html
   338      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   339      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   340      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   341      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   342      * Note: The destructor is not called unless the stored value is != NULL
   343      * Note: You can call this function any number of times for the same thread, there's no harm in it
   344      *       (except for some lost CPU cycles)
   345      */
   346     JNIEnv *env = Android_JNI_GetEnv();
   347     pthread_setspecific(mThreadKey, (void*) env);
   348     return 1;
   349 }
   350 
   351 //
   352 // Audio support
   353 //
   354 static jboolean audioBuffer16Bit = JNI_FALSE;
   355 static jboolean audioBufferStereo = JNI_FALSE;
   356 static jobject audioBuffer = NULL;
   357 static void* audioBufferPinned = NULL;
   358 
   359 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   360 {
   361     int audioBufferFrames;
   362 
   363     int status;
   364     JNIEnv *env = Android_JNI_GetEnv();
   365 
   366     if (!env) {
   367         LOGE("callback_handler: failed to attach current thread");
   368     }
   369     Android_JNI_SetupThread();
   370 
   371     
   372     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   373     audioBuffer16Bit = is16Bit;
   374     audioBufferStereo = channelCount > 1;
   375 
   376     audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   377 
   378     if (audioBuffer == NULL) {
   379         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
   380         return 0;
   381     }
   382     audioBuffer = env->NewGlobalRef(audioBuffer);
   383 
   384     jboolean isCopy = JNI_FALSE;
   385     if (audioBuffer16Bit) {
   386         audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   387         audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   388     } else {
   389         audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   390         audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   391     }
   392     if (audioBufferStereo) {
   393         audioBufferFrames /= 2;
   394     }
   395  
   396     return audioBufferFrames;
   397 }
   398 
   399 extern "C" void * Android_JNI_GetAudioBuffer()
   400 {
   401     return audioBufferPinned;
   402 }
   403 
   404 extern "C" void Android_JNI_WriteAudioBuffer()
   405 {
   406     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   407 
   408     if (audioBuffer16Bit) {
   409         mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   410         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   411     } else {
   412         mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   413         mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   414     }
   415 
   416     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   417 }
   418 
   419 extern "C" void Android_JNI_CloseAudioDevice()
   420 {
   421     int status;
   422     JNIEnv *env = Android_JNI_GetEnv();
   423 
   424     env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
   425 
   426     if (audioBuffer) {
   427         env->DeleteGlobalRef(audioBuffer);
   428         audioBuffer = NULL;
   429         audioBufferPinned = NULL;
   430     }
   431 }
   432 
   433 // Test for an exception and call SDL_SetError with its detail if one occurs
   434 static bool Android_JNI_ExceptionOccurred()
   435 {
   436     SDL_assert(LocalReferenceHolder::IsActive());
   437     JNIEnv *mEnv = Android_JNI_GetEnv();
   438 
   439     jthrowable exception = mEnv->ExceptionOccurred();
   440     if (exception != NULL) {
   441         jmethodID mid;
   442 
   443         // Until this happens most JNI operations have undefined behaviour
   444         mEnv->ExceptionClear();
   445 
   446         jclass exceptionClass = mEnv->GetObjectClass(exception);
   447         jclass classClass = mEnv->FindClass("java/lang/Class");
   448 
   449         mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   450         jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   451         const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   452 
   453         mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   454         jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   455 
   456         if (exceptionMessage != NULL) {
   457             const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
   458                     exceptionMessage, 0);
   459             SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   460             mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   461         } else {
   462             SDL_SetError("%s", exceptionNameUTF8);
   463         }
   464 
   465         mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   466 
   467         return true;
   468     }
   469 
   470     return false;
   471 }
   472 
   473 static int Android_JNI_FileOpen(SDL_RWops* ctx)
   474 {
   475     LocalReferenceHolder refs;
   476     int result = 0;
   477 
   478     jmethodID mid;
   479     jobject context;
   480     jobject assetManager;
   481     jobject inputStream;
   482     jclass channels;
   483     jobject readableByteChannel;
   484     jstring fileNameJString;
   485 
   486     JNIEnv *mEnv = Android_JNI_GetEnv();
   487     if (!refs.init(mEnv)) {
   488         goto failure;
   489     }
   490 
   491     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   492 
   493     // context = SDLActivity.getContext();
   494     mid = mEnv->GetStaticMethodID(mActivityClass,
   495             "getContext","()Landroid/content/Context;");
   496     context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   497 
   498     // assetManager = context.getAssets();
   499     mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   500             "getAssets", "()Landroid/content/res/AssetManager;");
   501     assetManager = mEnv->CallObjectMethod(context, mid);
   502 
   503     // inputStream = assetManager.open(<filename>);
   504     mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   505             "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
   506     inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   507     if (Android_JNI_ExceptionOccurred()) {
   508         goto failure;
   509     }
   510 
   511     ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   512 
   513     // Despite all the visible documentation on [Asset]InputStream claiming
   514     // that the .available() method is not guaranteed to return the entire file
   515     // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   516     // android/apis/content/ReadAsset.java imply that Android's
   517     // AssetInputStream.available() /will/ always return the total file size
   518 
   519     // size = inputStream.available();
   520     mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   521             "available", "()I");
   522     ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
   523     if (Android_JNI_ExceptionOccurred()) {
   524         goto failure;
   525     }
   526 
   527     // readableByteChannel = Channels.newChannel(inputStream);
   528     channels = mEnv->FindClass("java/nio/channels/Channels");
   529     mid = mEnv->GetStaticMethodID(channels,
   530             "newChannel",
   531             "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   532     readableByteChannel = mEnv->CallStaticObjectMethod(
   533             channels, mid, inputStream);
   534     if (Android_JNI_ExceptionOccurred()) {
   535         goto failure;
   536     }
   537 
   538     ctx->hidden.androidio.readableByteChannelRef =
   539         mEnv->NewGlobalRef(readableByteChannel);
   540 
   541     // Store .read id for reading purposes
   542     mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   543             "read", "(Ljava/nio/ByteBuffer;)I");
   544     ctx->hidden.androidio.readMethod = mid;
   545 
   546     ctx->hidden.androidio.position = 0;
   547 
   548     if (false) {
   549 failure:
   550         result = -1;
   551 
   552         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   553 
   554         if(ctx->hidden.androidio.inputStreamRef != NULL) {
   555             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   556         }
   557 
   558         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   559             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   560         }
   561 
   562     }
   563 
   564     return result;
   565 }
   566 
   567 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   568         const char* fileName, const char*)
   569 {
   570     LocalReferenceHolder refs;
   571     JNIEnv *mEnv = Android_JNI_GetEnv();
   572 
   573     if (!refs.init(mEnv)) {
   574         return -1;
   575     }
   576 
   577     if (!ctx) {
   578         return -1;
   579     }
   580 
   581     jstring fileNameJString = mEnv->NewStringUTF(fileName);
   582     ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   583     ctx->hidden.androidio.inputStreamRef = NULL;
   584     ctx->hidden.androidio.readableByteChannelRef = NULL;
   585     ctx->hidden.androidio.readMethod = NULL;
   586 
   587     return Android_JNI_FileOpen(ctx);
   588 }
   589 
   590 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   591         size_t size, size_t maxnum)
   592 {
   593     LocalReferenceHolder refs;
   594     jlong bytesRemaining = (jlong) (size * maxnum);
   595     jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   596     int bytesRead = 0;
   597 
   598     /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   599     if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   600 
   601     JNIEnv *mEnv = Android_JNI_GetEnv();
   602     if (!refs.init(mEnv)) {
   603         return -1;
   604     }
   605 
   606     jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   607     jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   608     jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   609 
   610     while (bytesRemaining > 0) {
   611         // result = readableByteChannel.read(...);
   612         int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   613 
   614         if (Android_JNI_ExceptionOccurred()) {
   615             return 0;
   616         }
   617 
   618         if (result < 0) {
   619             break;
   620         }
   621 
   622         bytesRemaining -= result;
   623         bytesRead += result;
   624         ctx->hidden.androidio.position += result;
   625     }
   626 
   627     return bytesRead / size;
   628 }
   629 
   630 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   631         size_t size, size_t num)
   632 {
   633     SDL_SetError("Cannot write to Android package filesystem");
   634     return 0;
   635 }
   636 
   637 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   638 {
   639     LocalReferenceHolder refs;
   640     int result = 0;
   641     JNIEnv *mEnv = Android_JNI_GetEnv();
   642 
   643     if (!refs.init(mEnv)) {
   644         SDL_SetError("Failed to allocate enough JVM local references");
   645         return -1;
   646     }
   647 
   648     if (ctx) {
   649         if (release) {
   650             mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   651         }
   652 
   653         jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   654 
   655         // inputStream.close();
   656         jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   657                 "close", "()V");
   658         mEnv->CallVoidMethod(inputStream, mid);
   659         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   660         mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   661         if (Android_JNI_ExceptionOccurred()) {
   662             result = -1;
   663         }
   664 
   665         if (release) {
   666             SDL_FreeRW(ctx);
   667         }
   668     }
   669 
   670     return result;
   671 }
   672 
   673 
   674 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
   675 {
   676     long newPosition;
   677 
   678     switch (whence) {
   679         case RW_SEEK_SET:
   680             newPosition = offset;
   681             break;
   682         case RW_SEEK_CUR:
   683             newPosition = ctx->hidden.androidio.position + offset;
   684             break;
   685         case RW_SEEK_END:
   686             newPosition = ctx->hidden.androidio.size + offset;
   687             break;
   688         default:
   689             SDL_SetError("Unknown value for 'whence'");
   690             return -1;
   691     }
   692     if (newPosition < 0) {
   693         newPosition = 0;
   694     }
   695     if (newPosition > ctx->hidden.androidio.size) {
   696         newPosition = ctx->hidden.androidio.size;
   697     }
   698 
   699     long movement = newPosition - ctx->hidden.androidio.position;
   700     jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   701 
   702     if (movement > 0) {
   703         unsigned char buffer[1024];
   704 
   705         // The easy case where we're seeking forwards
   706         while (movement > 0) {
   707             long amount = (long) sizeof (buffer);
   708             if (amount > movement) {
   709                 amount = movement;
   710             }
   711             size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   712 
   713             if (result <= 0) {
   714                 // Failed to read/skip the required amount, so fail
   715                 return -1;
   716             }
   717 
   718             movement -= result;
   719         }
   720     } else if (movement < 0) {
   721         // We can't seek backwards so we have to reopen the file and seek
   722         // forwards which obviously isn't very efficient
   723         Android_JNI_FileClose(ctx, false);
   724         Android_JNI_FileOpen(ctx);
   725         Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   726     }
   727 
   728     ctx->hidden.androidio.position = newPosition;
   729 
   730     return ctx->hidden.androidio.position;
   731 }
   732 
   733 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   734 {
   735     return Android_JNI_FileClose(ctx, true);
   736 }
   737 
   738 // returns a new global reference which needs to be released later
   739 static jobject Android_JNI_GetSystemServiceObject(const char* name)
   740 {
   741     LocalReferenceHolder refs;
   742     JNIEnv* env = Android_JNI_GetEnv();
   743     if (!refs.init(env)) {
   744         return NULL;
   745     }
   746 
   747     jstring service = env->NewStringUTF(name);
   748 
   749     jmethodID mid;
   750 
   751     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   752     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   753 
   754     mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   755     jobject manager = env->CallObjectMethod(context, mid, service);
   756 
   757     env->DeleteLocalRef(service);
   758 
   759     return manager ? env->NewGlobalRef(manager) : NULL;
   760 }
   761 
   762 #define SETUP_CLIPBOARD(error) \
   763     LocalReferenceHolder refs; \
   764     JNIEnv* env = Android_JNI_GetEnv(); \
   765     if (!refs.init(env)) { \
   766         return error; \
   767     } \
   768     jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   769     if (!clipboard) { \
   770         return error; \
   771     }
   772 
   773 extern "C" int Android_JNI_SetClipboardText(const char* text)
   774 {
   775     SETUP_CLIPBOARD(-1)
   776 
   777     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   778     jstring string = env->NewStringUTF(text);
   779     env->CallVoidMethod(clipboard, mid, string);
   780     env->DeleteGlobalRef(clipboard);
   781     env->DeleteLocalRef(string);
   782     return 0;
   783 }
   784 
   785 extern "C" char* Android_JNI_GetClipboardText()
   786 {
   787     SETUP_CLIPBOARD(SDL_strdup(""))
   788 
   789     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   790     jobject sequence = env->CallObjectMethod(clipboard, mid);
   791     env->DeleteGlobalRef(clipboard);
   792     if (sequence) {
   793         mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   794         jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   795         const char* utf = env->GetStringUTFChars(string, 0);
   796         if (utf) {
   797             char* text = SDL_strdup(utf);
   798             env->ReleaseStringUTFChars(string, utf);
   799             return text;
   800         }
   801     }
   802     return SDL_strdup("");
   803 }
   804 
   805 extern "C" SDL_bool Android_JNI_HasClipboardText()
   806 {
   807     SETUP_CLIPBOARD(SDL_FALSE)
   808 
   809     jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
   810     jboolean has = env->CallBooleanMethod(clipboard, mid);
   811     env->DeleteGlobalRef(clipboard);
   812     return has ? SDL_TRUE : SDL_FALSE;
   813 }
   814 
   815 
   816 // returns 0 on success or -1 on error (others undefined then)
   817 // returns truthy or falsy value in plugged, charged and battery
   818 // returns the value in seconds and percent or -1 if not available
   819 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
   820 {
   821     LocalReferenceHolder refs;
   822     JNIEnv* env = Android_JNI_GetEnv();
   823     if (!refs.init(env)) {
   824         return -1;
   825     }
   826 
   827     jmethodID mid;
   828 
   829     mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   830     jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   831 
   832     jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
   833 
   834     jclass cls = env->FindClass("android/content/IntentFilter");
   835 
   836     mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
   837     jobject filter = env->NewObject(cls, mid, action);
   838 
   839     env->DeleteLocalRef(action);
   840 
   841     mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
   842     jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
   843 
   844     env->DeleteLocalRef(filter);
   845 
   846     cls = env->GetObjectClass(intent);
   847 
   848     jstring iname;
   849     jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
   850 
   851 #define GET_INT_EXTRA(var, key) \
   852     iname = env->NewStringUTF(key); \
   853     int var = env->CallIntMethod(intent, imid, iname, -1); \
   854     env->DeleteLocalRef(iname);
   855 
   856     jstring bname;
   857     jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
   858 
   859 #define GET_BOOL_EXTRA(var, key) \
   860     bname = env->NewStringUTF(key); \
   861     int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
   862     env->DeleteLocalRef(bname);
   863 
   864     if (plugged) {
   865         GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
   866         if (plug == -1) {
   867             return -1;
   868         }
   869         // 1 == BatteryManager.BATTERY_PLUGGED_AC
   870         // 2 == BatteryManager.BATTERY_PLUGGED_USB
   871         *plugged = (0 < plug) ? 1 : 0;
   872     }
   873 
   874     if (charged) {
   875         GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
   876         if (status == -1) {
   877             return -1;
   878         }
   879         // 5 == BatteryManager.BATTERY_STATUS_FULL
   880         *charged = (status == 5) ? 1 : 0;
   881     }
   882 
   883     if (battery) {
   884         GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
   885         *battery = present ? 1 : 0;
   886     }
   887 
   888     if (seconds) {
   889         *seconds = -1; // not possible
   890     }
   891 
   892     if (percent) {
   893         GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
   894         GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
   895         if ((level == -1) || (scale == -1)) {
   896             return -1;
   897         }
   898         *percent = level * 100 / scale;
   899     }
   900 
   901     env->DeleteLocalRef(intent);
   902 
   903     return 0;
   904 }
   905 
   906 // sends message to be handled on the UI event dispatch thread
   907 extern "C" int Android_JNI_SendMessage(int command, int param)
   908 {
   909     JNIEnv *env = Android_JNI_GetEnv();
   910     if (!env) {
   911         return -1;
   912     }
   913     jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
   914     if (!mid) {
   915         return -1;
   916     }
   917     env->CallStaticVoidMethod(mActivityClass, mid, command, param);
   918     return 0;
   919 }
   920 
   921 #endif /* __ANDROID__ */
   922 
   923 /* vi: set ts=4 sw=4 expandtab: */