cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_android.c (57668B)


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