Android: Removed all unnecessary dependencies on C++.
authorEric Wing <ewing . public |-at-| gmail . com>
Mon, 22 Jul 2013 02:51:45 -0700
changeset 7501b27825bb5879
parent 7500 6b484ff38f86
child 7502 6ff02ff3cf06
Android: Removed all unnecessary dependencies on C++.

C++ is a bit of a minefield on Android. Much functionality still doesn't work, and Android can't decide on which C++ standard library to use, so it provides 3 different ones, all of which are incompatible with each other. (It looks like clang is coming too which will add a new compiler and a 4th standard library.)

As middleware, SDL might be distributed as a binary and intermixed with other projects already using C++. If C++ is intermixed in a bad way, bad things will happen. Removing dependencies on C++ will avoid this problem and downstream users won't have to worry/care.
Android.mk
README-android.txt
android-project/jni/src/Android.mk
src/core/android/SDL_android.c
src/core/android/SDL_android.cpp
src/main/android/SDL_android_main.c
src/main/android/SDL_android_main.cpp
     1.1 --- a/Android.mk	Mon Jul 22 22:54:00 2013 +0200
     1.2 +++ b/Android.mk	Mon Jul 22 02:51:45 2013 -0700
     1.3 @@ -22,7 +22,7 @@
     1.4  	$(wildcard $(LOCAL_PATH)/src/audio/dummy/*.c) \
     1.5  	$(LOCAL_PATH)/src/atomic/SDL_atomic.c \
     1.6  	$(LOCAL_PATH)/src/atomic/SDL_spinlock.c.arm \
     1.7 -	$(wildcard $(LOCAL_PATH)/src/core/android/*.cpp) \
     1.8 +	$(wildcard $(LOCAL_PATH)/src/core/android/*.c) \
     1.9  	$(wildcard $(LOCAL_PATH)/src/cpuinfo/*.c) \
    1.10  	$(wildcard $(LOCAL_PATH)/src/events/*.c) \
    1.11  	$(wildcard $(LOCAL_PATH)/src/file/*.c) \
     2.1 --- a/README-android.txt	Mon Jul 22 22:54:00 2013 +0200
     2.2 +++ b/README-android.txt	Mon Jul 22 02:51:45 2013 -0700
     2.3 @@ -28,10 +28,10 @@
     2.4  
     2.5  The Java code loads your game code, the SDL shared library, and
     2.6  dispatches to native functions implemented in the SDL library:
     2.7 -src/SDL_android.cpp
     2.8 +src/SDL_android.c
     2.9  
    2.10  Your project must include some glue code that starts your main() routine:
    2.11 -src/main/android/SDL_android_main.cpp
    2.12 +src/main/android/SDL_android_main.c
    2.13  
    2.14  
    2.15  ================================================================================
     3.1 --- a/android-project/jni/src/Android.mk	Mon Jul 22 22:54:00 2013 +0200
     3.2 +++ b/android-project/jni/src/Android.mk	Mon Jul 22 02:51:45 2013 -0700
     3.3 @@ -9,7 +9,7 @@
     3.4  LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include
     3.5  
     3.6  # Add your application source files here...
     3.7 -LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.cpp \
     3.8 +LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \
     3.9  	YourSourceHere.c
    3.10  
    3.11  LOCAL_SHARED_LIBRARIES := SDL2
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/core/android/SDL_android.c	Mon Jul 22 02:51:45 2013 -0700
     4.3 @@ -0,0 +1,1353 @@
     4.4 +/*
     4.5 +  Simple DirectMedia Layer
     4.6 +  Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
     4.7 +
     4.8 +  This software is provided 'as-is', without any express or implied
     4.9 +  warranty.  In no event will the authors be held liable for any damages
    4.10 +  arising from the use of this software.
    4.11 +
    4.12 +  Permission is granted to anyone to use this software for any purpose,
    4.13 +  including commercial applications, and to alter it and redistribute it
    4.14 +  freely, subject to the following restrictions:
    4.15 +
    4.16 +  1. The origin of this software must not be misrepresented; you must not
    4.17 +     claim that you wrote the original software. If you use this software
    4.18 +     in a product, an acknowledgment in the product documentation would be
    4.19 +     appreciated but is not required.
    4.20 +  2. Altered source versions must be plainly marked as such, and must not be
    4.21 +     misrepresented as being the original software.
    4.22 +  3. This notice may not be removed or altered from any source distribution.
    4.23 +*/
    4.24 +#include "SDL_config.h"
    4.25 +#include "SDL_stdinc.h"
    4.26 +#include "SDL_assert.h"
    4.27 +#include "SDL_log.h"
    4.28 +
    4.29 +#ifdef __ANDROID__
    4.30 +
    4.31 +#include "SDL_system.h"
    4.32 +#include "SDL_android.h"
    4.33 +#include <EGL/egl.h>
    4.34 +
    4.35 +#include "../../events/SDL_events_c.h"
    4.36 +#include "../../video/android/SDL_androidkeyboard.h"
    4.37 +#include "../../video/android/SDL_androidtouch.h"
    4.38 +#include "../../video/android/SDL_androidvideo.h"
    4.39 +
    4.40 +#include <android/log.h>
    4.41 +#include <pthread.h>
    4.42 +#include <sys/types.h>
    4.43 +#include <unistd.h>
    4.44 +#define LOG_TAG "SDL_android"
    4.45 +//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    4.46 +//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    4.47 +#define LOGI(...) do {} while (false)
    4.48 +#define LOGE(...) do {} while (false)
    4.49 +
    4.50 +/* Uncomment this to log messages entering and exiting methods in this file */
    4.51 +//#define DEBUG_JNI
    4.52 +
    4.53 +/* Implemented in audio/android/SDL_androidaudio.c */
    4.54 +extern void Android_RunAudioThread();
    4.55 +
    4.56 +static void Android_JNI_ThreadDestroyed(void*);
    4.57 +
    4.58 +/*******************************************************************************
    4.59 + This file links the Java side of Android with libsdl
    4.60 +*******************************************************************************/
    4.61 +#include <jni.h>
    4.62 +#include <android/log.h>
    4.63 +#include <stdbool.h>
    4.64 +
    4.65 +
    4.66 +/*******************************************************************************
    4.67 +                               Globals
    4.68 +*******************************************************************************/
    4.69 +static pthread_key_t mThreadKey;
    4.70 +static JavaVM* mJavaVM;
    4.71 +
    4.72 +// Main activity
    4.73 +static jclass mActivityClass;
    4.74 +
    4.75 +// method signatures
    4.76 +static jmethodID midCreateGLContext;
    4.77 +static jmethodID midFlipBuffers;
    4.78 +static jmethodID midAudioInit;
    4.79 +static jmethodID midAudioWriteShortBuffer;
    4.80 +static jmethodID midAudioWriteByteBuffer;
    4.81 +static jmethodID midAudioQuit;
    4.82 +
    4.83 +// Accelerometer data storage
    4.84 +static float fLastAccelerometer[3];
    4.85 +static bool bHasNewData;
    4.86 +
    4.87 +/*******************************************************************************
    4.88 +                 Functions called by JNI
    4.89 +*******************************************************************************/
    4.90 +
    4.91 +// Library init
    4.92 +jint JNI_OnLoad(JavaVM* vm, void* reserved)
    4.93 +{
    4.94 +    JNIEnv *env;
    4.95 +    mJavaVM = vm;
    4.96 +    LOGI("JNI_OnLoad called");
    4.97 +    if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    4.98 +        LOGE("Failed to get the environment using GetEnv()");
    4.99 +        return -1;
   4.100 +    }
   4.101 +    /*
   4.102 +     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   4.103 +     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   4.104 +     */
   4.105 +    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
   4.106 +        __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   4.107 +    }
   4.108 +    else {
   4.109 +        Android_JNI_SetupThread();
   4.110 +    }
   4.111 +
   4.112 +    return JNI_VERSION_1_4;
   4.113 +}
   4.114 +
   4.115 +// Called before SDL_main() to initialize JNI bindings
   4.116 +void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   4.117 +{
   4.118 +    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   4.119 +
   4.120 +    Android_JNI_SetupThread();
   4.121 +
   4.122 +    mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
   4.123 +
   4.124 +    midCreateGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.125 +                                "createGLContext","(II[I)Z");
   4.126 +    midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.127 +                                "flipBuffers","()V");
   4.128 +    midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.129 +                                "audioInit", "(IZZI)V");
   4.130 +    midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.131 +                                "audioWriteShortBuffer", "([S)V");
   4.132 +    midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.133 +                                "audioWriteByteBuffer", "([B)V");
   4.134 +    midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.135 +                                "audioQuit", "()V");
   4.136 +
   4.137 +    bHasNewData = false;
   4.138 +
   4.139 +    if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   4.140 +       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   4.141 +        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   4.142 +    }
   4.143 +    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   4.144 +}
   4.145 +
   4.146 +// Resize
   4.147 +void Java_org_libsdl_app_SDLActivity_onNativeResize(
   4.148 +                                    JNIEnv* env, jclass jcls,
   4.149 +                                    jint width, jint height, jint format)
   4.150 +{
   4.151 +    Android_SetScreenResolution(width, height, format);
   4.152 +}
   4.153 +
   4.154 +// Keydown
   4.155 +void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   4.156 +                                    JNIEnv* env, jclass jcls, jint keycode)
   4.157 +{
   4.158 +    Android_OnKeyDown(keycode);
   4.159 +}
   4.160 +
   4.161 +// Keyup
   4.162 +void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   4.163 +                                    JNIEnv* env, jclass jcls, jint keycode)
   4.164 +{
   4.165 +    Android_OnKeyUp(keycode);
   4.166 +}
   4.167 +
   4.168 +// Touch
   4.169 +void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   4.170 +                                    JNIEnv* env, jclass jcls,
   4.171 +                                    jint touch_device_id_in, jint pointer_finger_id_in,
   4.172 +                                    jint action, jfloat x, jfloat y, jfloat p)
   4.173 +{
   4.174 +    Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   4.175 +}
   4.176 +
   4.177 +// Accelerometer
   4.178 +void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   4.179 +                                    JNIEnv* env, jclass jcls,
   4.180 +                                    jfloat x, jfloat y, jfloat z)
   4.181 +{
   4.182 +    fLastAccelerometer[0] = x;
   4.183 +    fLastAccelerometer[1] = y;
   4.184 +    fLastAccelerometer[2] = z;
   4.185 +    bHasNewData = true;
   4.186 +}
   4.187 +
   4.188 +// Low memory
   4.189 +void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
   4.190 +                                    JNIEnv* env, jclass cls)
   4.191 +{
   4.192 +    SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   4.193 +}
   4.194 +
   4.195 +// Quit
   4.196 +void Java_org_libsdl_app_SDLActivity_nativeQuit(
   4.197 +                                    JNIEnv* env, jclass cls)
   4.198 +{
   4.199 +    // Inject a SDL_QUIT event
   4.200 +    SDL_SendQuit();
   4.201 +    SDL_SendAppEvent(SDL_APP_TERMINATING);
   4.202 +}
   4.203 +
   4.204 +// Pause
   4.205 +void Java_org_libsdl_app_SDLActivity_nativePause(
   4.206 +                                    JNIEnv* env, jclass cls)
   4.207 +{
   4.208 +    if (Android_Window) {
   4.209 +        /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   4.210 +        if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   4.211 +        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   4.212 +        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   4.213 +    }
   4.214 +
   4.215 +    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
   4.216 +    SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   4.217 +    SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   4.218 +}
   4.219 +
   4.220 +// Resume
   4.221 +void Java_org_libsdl_app_SDLActivity_nativeResume(
   4.222 +                                    JNIEnv* env, jclass cls)
   4.223 +{
   4.224 +    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
   4.225 +    SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   4.226 +    SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   4.227 +
   4.228 +    if (Android_Window) {
   4.229 +        /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   4.230 +         * We can't restore the GL Context here because it needs to be done on the SDL main thread
   4.231 +         * and this function will be called from the Java thread instead.
   4.232 +         */
   4.233 +        if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   4.234 +        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   4.235 +        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   4.236 +    }
   4.237 +}
   4.238 +
   4.239 +void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   4.240 +                                    JNIEnv* env, jclass cls)
   4.241 +{
   4.242 +    /* This is the audio thread, with a different environment */
   4.243 +    Android_JNI_SetupThread();
   4.244 +
   4.245 +    Android_RunAudioThread();
   4.246 +}
   4.247 +
   4.248 +void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
   4.249 +                                    JNIEnv* env, jclass cls,
   4.250 +                                    jstring text, jint newCursorPosition)
   4.251 +{
   4.252 +    const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   4.253 +
   4.254 +    SDL_SendKeyboardText(utftext);
   4.255 +
   4.256 +    (*env)->ReleaseStringUTFChars(env, text, utftext);
   4.257 +}
   4.258 +
   4.259 +void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
   4.260 +                                    JNIEnv* env, jclass cls,
   4.261 +                                    jstring text, jint newCursorPosition)
   4.262 +{
   4.263 +    const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
   4.264 +
   4.265 +    SDL_SendEditingText(utftext, 0, 0);
   4.266 +
   4.267 +    (*env)->ReleaseStringUTFChars(env, text, utftext);
   4.268 +}
   4.269 +
   4.270 +
   4.271 +
   4.272 +/*******************************************************************************
   4.273 +             Functions called by SDL into Java
   4.274 +*******************************************************************************/
   4.275 +
   4.276 +static int s_active = 0;
   4.277 +struct LocalReferenceHolder
   4.278 +{
   4.279 +    JNIEnv *m_env;
   4.280 +    const char *m_func;
   4.281 +};
   4.282 +
   4.283 +static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
   4.284 +{
   4.285 +    struct LocalReferenceHolder refholder;
   4.286 +    refholder.m_env = NULL;
   4.287 +    refholder.m_func = func;
   4.288 +#ifdef DEBUG_JNI
   4.289 +    SDL_Log("Entering function %s", func);
   4.290 +#endif
   4.291 +    return refholder;
   4.292 +}
   4.293 +
   4.294 +static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
   4.295 +{
   4.296 +    const int capacity = 16;
   4.297 +    if ((*env)->PushLocalFrame(env, capacity) < 0) {
   4.298 +        SDL_SetError("Failed to allocate enough JVM local references");
   4.299 +        return false;
   4.300 +    }
   4.301 +    ++s_active;
   4.302 +    refholder->m_env = env;
   4.303 +    return SDL_TRUE;
   4.304 +}
   4.305 +
   4.306 +static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
   4.307 +{
   4.308 +#ifdef DEBUG_JNI
   4.309 +    SDL_Log("Leaving function %s", refholder->m_func);
   4.310 +#endif
   4.311 +    if (refholder->m_env) {
   4.312 +        JNIEnv* env = refholder->m_env;
   4.313 +        (*env)->PopLocalFrame(env, NULL);
   4.314 +        --s_active;
   4.315 +    }
   4.316 +}
   4.317 +
   4.318 +static SDL_bool LocalReferenceHolder_IsActive()
   4.319 +{
   4.320 +    return s_active > 0;    
   4.321 +}
   4.322 +
   4.323 +
   4.324 +SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
   4.325 +                                int red, int green, int blue, int alpha,
   4.326 +                                int buffer, int depth, int stencil,
   4.327 +                                int buffers, int samples)
   4.328 +{
   4.329 +    JNIEnv *env = Android_JNI_GetEnv();
   4.330 +
   4.331 +    jint attribs[] = {
   4.332 +        EGL_RED_SIZE, red,
   4.333 +        EGL_GREEN_SIZE, green,
   4.334 +        EGL_BLUE_SIZE, blue,
   4.335 +        EGL_ALPHA_SIZE, alpha,
   4.336 +        EGL_BUFFER_SIZE, buffer,
   4.337 +        EGL_DEPTH_SIZE, depth,
   4.338 +        EGL_STENCIL_SIZE, stencil,
   4.339 +        EGL_SAMPLE_BUFFERS, buffers,
   4.340 +        EGL_SAMPLES, samples,
   4.341 +        EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
   4.342 +        EGL_NONE
   4.343 +    };
   4.344 +    int len = SDL_arraysize(attribs);
   4.345 +
   4.346 +    jintArray array;
   4.347 +
   4.348 +    array = (*env)->NewIntArray(env, len);
   4.349 +    (*env)->SetIntArrayRegion(env, array, 0, len, attribs);
   4.350 +
   4.351 +    jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
   4.352 +
   4.353 +    (*env)->DeleteLocalRef(env, array);
   4.354 +
   4.355 +    return success ? SDL_TRUE : SDL_FALSE;
   4.356 +}
   4.357 +
   4.358 +void Android_JNI_SwapWindow()
   4.359 +{
   4.360 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.361 +    (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers);
   4.362 +}
   4.363 +
   4.364 +void Android_JNI_SetActivityTitle(const char *title)
   4.365 +{
   4.366 +    jmethodID mid;
   4.367 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.368 +    mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
   4.369 +    if (mid) {
   4.370 +        jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
   4.371 +        (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle);
   4.372 +        (*mEnv)->DeleteLocalRef(mEnv, jtitle);
   4.373 +    }
   4.374 +}
   4.375 +
   4.376 +SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   4.377 +{
   4.378 +    int i;
   4.379 +    SDL_bool retval = SDL_FALSE;
   4.380 +
   4.381 +    if (bHasNewData) {
   4.382 +        for (i = 0; i < 3; ++i) {
   4.383 +            values[i] = fLastAccelerometer[i];
   4.384 +        }
   4.385 +        bHasNewData = false;
   4.386 +        retval = SDL_TRUE;
   4.387 +    }
   4.388 +
   4.389 +    return retval;
   4.390 +}
   4.391 +
   4.392 +static void Android_JNI_ThreadDestroyed(void* value) {
   4.393 +    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   4.394 +    JNIEnv *env = (JNIEnv*) value;
   4.395 +    if (env != NULL) {
   4.396 +        (*mJavaVM)->DetachCurrentThread(mJavaVM);
   4.397 +        pthread_setspecific(mThreadKey, NULL);
   4.398 +    }
   4.399 +}
   4.400 +
   4.401 +JNIEnv* Android_JNI_GetEnv(void) {
   4.402 +    /* From http://developer.android.com/guide/practices/jni.html
   4.403 +     * All threads are Linux threads, scheduled by the kernel.
   4.404 +     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   4.405 +     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   4.406 +     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   4.407 +     * and cannot make JNI calls.
   4.408 +     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   4.409 +     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   4.410 +     * is a no-op.
   4.411 +     * Note: You can call this function any number of times for the same thread, there's no harm in it
   4.412 +     */
   4.413 +
   4.414 +    JNIEnv *env;
   4.415 +    int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
   4.416 +    if(status < 0) {
   4.417 +        LOGE("failed to attach current thread");
   4.418 +        return 0;
   4.419 +    }
   4.420 +
   4.421 +    return env;
   4.422 +}
   4.423 +
   4.424 +int Android_JNI_SetupThread(void) {
   4.425 +    /* From http://developer.android.com/guide/practices/jni.html
   4.426 +     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   4.427 +     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   4.428 +     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   4.429 +     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   4.430 +     * Note: The destructor is not called unless the stored value is != NULL
   4.431 +     * Note: You can call this function any number of times for the same thread, there's no harm in it
   4.432 +     *       (except for some lost CPU cycles)
   4.433 +     */
   4.434 +    JNIEnv *env = Android_JNI_GetEnv();
   4.435 +    pthread_setspecific(mThreadKey, (void*) env);
   4.436 +    return 1;
   4.437 +}
   4.438 +
   4.439 +//
   4.440 +// Audio support
   4.441 +//
   4.442 +static jboolean audioBuffer16Bit = JNI_FALSE;
   4.443 +static jboolean audioBufferStereo = JNI_FALSE;
   4.444 +static jobject audioBuffer = NULL;
   4.445 +static void* audioBufferPinned = NULL;
   4.446 +
   4.447 +int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   4.448 +{
   4.449 +    int audioBufferFrames;
   4.450 +
   4.451 +    JNIEnv *env = Android_JNI_GetEnv();
   4.452 +
   4.453 +    if (!env) {
   4.454 +        LOGE("callback_handler: failed to attach current thread");
   4.455 +    }
   4.456 +    Android_JNI_SetupThread();
   4.457 +
   4.458 +    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   4.459 +    audioBuffer16Bit = is16Bit;
   4.460 +    audioBufferStereo = channelCount > 1;
   4.461 +
   4.462 +    (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   4.463 +
   4.464 +    /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   4.465 +     * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   4.466 +
   4.467 +    if (is16Bit) {
   4.468 +        jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   4.469 +        if (audioBufferLocal) {
   4.470 +            audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.471 +            (*env)->DeleteLocalRef(env, audioBufferLocal);
   4.472 +        }
   4.473 +    }
   4.474 +    else {
   4.475 +        jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   4.476 +        if (audioBufferLocal) {
   4.477 +            audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal);
   4.478 +            (*env)->DeleteLocalRef(env, audioBufferLocal);
   4.479 +        }
   4.480 +    }
   4.481 +
   4.482 +    if (audioBuffer == NULL) {
   4.483 +        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   4.484 +        return 0;
   4.485 +    }
   4.486 +
   4.487 +    jboolean isCopy = JNI_FALSE;
   4.488 +    if (audioBuffer16Bit) {
   4.489 +        audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
   4.490 +        audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
   4.491 +    } else {
   4.492 +        audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
   4.493 +        audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
   4.494 +    }
   4.495 +    if (audioBufferStereo) {
   4.496 +        audioBufferFrames /= 2;
   4.497 +    }
   4.498 +
   4.499 +    return audioBufferFrames;
   4.500 +}
   4.501 +
   4.502 +void * Android_JNI_GetAudioBuffer()
   4.503 +{
   4.504 +    return audioBufferPinned;
   4.505 +}
   4.506 +
   4.507 +void Android_JNI_WriteAudioBuffer()
   4.508 +{
   4.509 +    JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   4.510 +
   4.511 +    if (audioBuffer16Bit) {
   4.512 +        (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   4.513 +        (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   4.514 +    } else {
   4.515 +        (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   4.516 +        (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   4.517 +    }
   4.518 +
   4.519 +    /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   4.520 +}
   4.521 +
   4.522 +void Android_JNI_CloseAudioDevice()
   4.523 +{
   4.524 +    JNIEnv *env = Android_JNI_GetEnv();
   4.525 +
   4.526 +    (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit);
   4.527 +
   4.528 +    if (audioBuffer) {
   4.529 +        (*env)->DeleteGlobalRef(env, audioBuffer);
   4.530 +        audioBuffer = NULL;
   4.531 +        audioBufferPinned = NULL;
   4.532 +    }
   4.533 +}
   4.534 +
   4.535 +// Test for an exception and call SDL_SetError with its detail if one occurs
   4.536 +// If optional parameter silent is truthy then SDL_SetError() is not called.
   4.537 +static bool Android_JNI_ExceptionOccurred(bool silent)
   4.538 +{
   4.539 +    SDL_assert(LocalReferenceHolder_IsActive());
   4.540 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.541 +
   4.542 +    jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv);
   4.543 +    if (exception != NULL) {
   4.544 +        jmethodID mid;
   4.545 +
   4.546 +        // Until this happens most JNI operations have undefined behaviour
   4.547 +        (*mEnv)->ExceptionClear(mEnv);
   4.548 +
   4.549 +        if (!silent) {
   4.550 +            jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
   4.551 +            jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
   4.552 +
   4.553 +            mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
   4.554 +            jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
   4.555 +            const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
   4.556 +
   4.557 +            mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
   4.558 +            jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
   4.559 +
   4.560 +            if (exceptionMessage != NULL) {
   4.561 +                const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
   4.562 +                SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   4.563 +                (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
   4.564 +            } else {
   4.565 +                SDL_SetError("%s", exceptionNameUTF8);
   4.566 +            }
   4.567 +
   4.568 +            (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
   4.569 +        }
   4.570 +
   4.571 +        return true;
   4.572 +    }
   4.573 +
   4.574 +    return false;
   4.575 +}
   4.576 +
   4.577 +static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
   4.578 +{
   4.579 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   4.580 +
   4.581 +    int result = 0;
   4.582 +
   4.583 +    jmethodID mid;
   4.584 +    jobject context;
   4.585 +    jobject assetManager;
   4.586 +    jobject inputStream;
   4.587 +    jclass channels;
   4.588 +    jobject readableByteChannel;
   4.589 +    jstring fileNameJString;
   4.590 +    jobject fd;
   4.591 +    jclass fdCls;
   4.592 +    jfieldID descriptor;
   4.593 +
   4.594 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.595 +    if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   4.596 +        goto failure;
   4.597 +    }
   4.598 +
   4.599 +    fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   4.600 +    ctx->hidden.androidio.position = 0;
   4.601 +
   4.602 +    // context = SDLActivity.getContext();
   4.603 +    mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
   4.604 +            "getContext","()Landroid/content/Context;");
   4.605 +    context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
   4.606 +
   4.607 +
   4.608 +    // assetManager = context.getAssets();
   4.609 +    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
   4.610 +            "getAssets", "()Landroid/content/res/AssetManager;");
   4.611 +    assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
   4.612 +
   4.613 +    /* First let's try opening the file to obtain an AssetFileDescriptor.
   4.614 +    * This method reads the files directly from the APKs using standard *nix calls
   4.615 +    */
   4.616 +    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   4.617 +    inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
   4.618 +    if (Android_JNI_ExceptionOccurred(true)) {
   4.619 +        goto fallback;
   4.620 +    }
   4.621 +
   4.622 +    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
   4.623 +    ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   4.624 +    if (Android_JNI_ExceptionOccurred(true)) {
   4.625 +        goto fallback;
   4.626 +    }
   4.627 +
   4.628 +    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
   4.629 +    ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
   4.630 +    if (Android_JNI_ExceptionOccurred(true)) {
   4.631 +        goto fallback;
   4.632 +    }
   4.633 +
   4.634 +    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   4.635 +    fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
   4.636 +    fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
   4.637 +    descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
   4.638 +    ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
   4.639 +    ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   4.640 +
   4.641 +    // Seek to the correct offset in the file.
   4.642 +    lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   4.643 +
   4.644 +    if (false) {
   4.645 +fallback:
   4.646 +        // Disabled log message because of spam on the Nexus 7
   4.647 +        //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   4.648 +
   4.649 +        /* Try the old method using InputStream */
   4.650 +        ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   4.651 +
   4.652 +        // inputStream = assetManager.open(<filename>);
   4.653 +        mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
   4.654 +                "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   4.655 +        inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   4.656 +        if (Android_JNI_ExceptionOccurred(false)) {
   4.657 +            goto failure;
   4.658 +        }
   4.659 +
   4.660 +        ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
   4.661 +
   4.662 +        // Despite all the visible documentation on [Asset]InputStream claiming
   4.663 +        // that the .available() method is not guaranteed to return the entire file
   4.664 +        // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   4.665 +        // android/apis/content/ReadAsset.java imply that Android's
   4.666 +        // AssetInputStream.available() /will/ always return the total file size
   4.667 +
   4.668 +        // size = inputStream.available();
   4.669 +        mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   4.670 +                "available", "()I");
   4.671 +        ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
   4.672 +        if (Android_JNI_ExceptionOccurred(false)) {
   4.673 +            goto failure;
   4.674 +        }
   4.675 +
   4.676 +        // readableByteChannel = Channels.newChannel(inputStream);
   4.677 +        channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
   4.678 +        mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
   4.679 +                "newChannel",
   4.680 +                "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   4.681 +        readableByteChannel = (*mEnv)->CallStaticObjectMethod(
   4.682 +                mEnv, channels, mid, inputStream);
   4.683 +        if (Android_JNI_ExceptionOccurred(false)) {
   4.684 +            goto failure;
   4.685 +        }
   4.686 +
   4.687 +        ctx->hidden.androidio.readableByteChannelRef =
   4.688 +            (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
   4.689 +
   4.690 +        // Store .read id for reading purposes
   4.691 +        mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
   4.692 +                "read", "(Ljava/nio/ByteBuffer;)I");
   4.693 +        ctx->hidden.androidio.readMethod = mid;
   4.694 +    }
   4.695 +
   4.696 +    if (false) {
   4.697 +failure:
   4.698 +        result = -1;
   4.699 +
   4.700 +        (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   4.701 +
   4.702 +        if(ctx->hidden.androidio.inputStreamRef != NULL) {
   4.703 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   4.704 +        }
   4.705 +
   4.706 +        if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   4.707 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   4.708 +        }
   4.709 +
   4.710 +        if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   4.711 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   4.712 +        }
   4.713 +
   4.714 +    }
   4.715 +    
   4.716 +    LocalReferenceHolder_Cleanup(&refs);
   4.717 +    return result;
   4.718 +}
   4.719 +
   4.720 +int Android_JNI_FileOpen(SDL_RWops* ctx,
   4.721 +        const char* fileName, const char* mode)
   4.722 +{
   4.723 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   4.724 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.725 +    int retval;
   4.726 +
   4.727 +    if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   4.728 +        LocalReferenceHolder_Cleanup(&refs);        
   4.729 +        return -1;
   4.730 +    }
   4.731 +
   4.732 +    if (!ctx) {
   4.733 +        LocalReferenceHolder_Cleanup(&refs);
   4.734 +        return -1;
   4.735 +    }
   4.736 +
   4.737 +    jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
   4.738 +    ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
   4.739 +    ctx->hidden.androidio.inputStreamRef = NULL;
   4.740 +    ctx->hidden.androidio.readableByteChannelRef = NULL;
   4.741 +    ctx->hidden.androidio.readMethod = NULL;
   4.742 +    ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   4.743 +
   4.744 +    retval = Internal_Android_JNI_FileOpen(ctx);
   4.745 +    LocalReferenceHolder_Cleanup(&refs);
   4.746 +    return retval;
   4.747 +}
   4.748 +
   4.749 +size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   4.750 +        size_t size, size_t maxnum)
   4.751 +{
   4.752 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   4.753 +
   4.754 +    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   4.755 +        size_t bytesMax = size * maxnum;
   4.756 +        if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   4.757 +            bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   4.758 +        }
   4.759 +        size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   4.760 +        if (result > 0) {
   4.761 +            ctx->hidden.androidio.position += result;
   4.762 +            LocalReferenceHolder_Cleanup(&refs);
   4.763 +            return result / size;
   4.764 +        }
   4.765 +        LocalReferenceHolder_Cleanup(&refs);
   4.766 +        return 0;
   4.767 +    } else {
   4.768 +        jlong bytesRemaining = (jlong) (size * maxnum);
   4.769 +        jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   4.770 +        int bytesRead = 0;
   4.771 +
   4.772 +        /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   4.773 +        if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   4.774 +
   4.775 +        JNIEnv *mEnv = Android_JNI_GetEnv();
   4.776 +        if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   4.777 +            LocalReferenceHolder_Cleanup(&refs);            
   4.778 +            return 0;
   4.779 +        }
   4.780 +
   4.781 +        jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   4.782 +        jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   4.783 +        jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
   4.784 +
   4.785 +        while (bytesRemaining > 0) {
   4.786 +            // result = readableByteChannel.read(...);
   4.787 +            int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
   4.788 +
   4.789 +            if (Android_JNI_ExceptionOccurred(false)) {
   4.790 +                LocalReferenceHolder_Cleanup(&refs);            
   4.791 +                return 0;
   4.792 +            }
   4.793 +
   4.794 +            if (result < 0) {
   4.795 +                break;
   4.796 +            }
   4.797 +
   4.798 +            bytesRemaining -= result;
   4.799 +            bytesRead += result;
   4.800 +            ctx->hidden.androidio.position += result;
   4.801 +        }
   4.802 +        LocalReferenceHolder_Cleanup(&refs);                    
   4.803 +        return bytesRead / size;
   4.804 +    }
   4.805 +    LocalReferenceHolder_Cleanup(&refs);            
   4.806 +}
   4.807 +
   4.808 +size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   4.809 +        size_t size, size_t num)
   4.810 +{
   4.811 +    SDL_SetError("Cannot write to Android package filesystem");
   4.812 +    return 0;
   4.813 +}
   4.814 +
   4.815 +static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   4.816 +{
   4.817 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   4.818 +
   4.819 +    int result = 0;
   4.820 +    JNIEnv *mEnv = Android_JNI_GetEnv();
   4.821 +
   4.822 +    if (!LocalReferenceHolder_Init(&refs, mEnv)) {
   4.823 +        LocalReferenceHolder_Cleanup(&refs);
   4.824 +        return SDL_SetError("Failed to allocate enough JVM local references");
   4.825 +    }
   4.826 +
   4.827 +    if (ctx) {
   4.828 +        if (release) {
   4.829 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
   4.830 +        }
   4.831 +
   4.832 +        if (ctx->hidden.androidio.assetFileDescriptorRef) {
   4.833 +            jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   4.834 +            jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   4.835 +                    "close", "()V");
   4.836 +            (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   4.837 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   4.838 +            if (Android_JNI_ExceptionOccurred(false)) {
   4.839 +                result = -1;
   4.840 +            }
   4.841 +        }
   4.842 +        else {
   4.843 +            jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   4.844 +
   4.845 +            // inputStream.close();
   4.846 +            jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
   4.847 +                    "close", "()V");
   4.848 +            (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
   4.849 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
   4.850 +            (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
   4.851 +            if (Android_JNI_ExceptionOccurred(false)) {
   4.852 +                result = -1;
   4.853 +            }
   4.854 +        }
   4.855 +
   4.856 +        if (release) {
   4.857 +            SDL_FreeRW(ctx);
   4.858 +        }
   4.859 +    }
   4.860 +
   4.861 +    LocalReferenceHolder_Cleanup(&refs);
   4.862 +    return result;
   4.863 +}
   4.864 +
   4.865 +
   4.866 +Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   4.867 +{
   4.868 +    return ctx->hidden.androidio.size;
   4.869 +}
   4.870 +
   4.871 +Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   4.872 +{
   4.873 +    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   4.874 +        switch (whence) {
   4.875 +            case RW_SEEK_SET:
   4.876 +                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   4.877 +                offset += ctx->hidden.androidio.offset;
   4.878 +                break;
   4.879 +            case RW_SEEK_CUR:
   4.880 +                offset += ctx->hidden.androidio.position;
   4.881 +                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   4.882 +                offset += ctx->hidden.androidio.offset;
   4.883 +                break;
   4.884 +            case RW_SEEK_END:
   4.885 +                offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   4.886 +                break;
   4.887 +            default:
   4.888 +                return SDL_SetError("Unknown value for 'whence'");
   4.889 +        }
   4.890 +        whence = SEEK_SET;
   4.891 +
   4.892 +        off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   4.893 +        if (ret == -1) return -1;
   4.894 +        ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   4.895 +    } else {
   4.896 +        Sint64 newPosition;
   4.897 +
   4.898 +        switch (whence) {
   4.899 +            case RW_SEEK_SET:
   4.900 +                newPosition = offset;
   4.901 +                break;
   4.902 +            case RW_SEEK_CUR:
   4.903 +                newPosition = ctx->hidden.androidio.position + offset;
   4.904 +                break;
   4.905 +            case RW_SEEK_END:
   4.906 +                newPosition = ctx->hidden.androidio.size + offset;
   4.907 +                break;
   4.908 +            default:
   4.909 +                return SDL_SetError("Unknown value for 'whence'");
   4.910 +        }
   4.911 +
   4.912 +        /* Validate the new position */
   4.913 +        if (newPosition < 0) {
   4.914 +            return SDL_Error(SDL_EFSEEK);
   4.915 +        }
   4.916 +        if (newPosition > ctx->hidden.androidio.size) {
   4.917 +            newPosition = ctx->hidden.androidio.size;
   4.918 +        }
   4.919 +
   4.920 +        Sint64 movement = newPosition - ctx->hidden.androidio.position;
   4.921 +        if (movement > 0) {
   4.922 +            unsigned char buffer[4096];
   4.923 +
   4.924 +            // The easy case where we're seeking forwards
   4.925 +            while (movement > 0) {
   4.926 +                Sint64 amount = sizeof (buffer);
   4.927 +                if (amount > movement) {
   4.928 +                    amount = movement;
   4.929 +                }
   4.930 +                size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   4.931 +                if (result <= 0) {
   4.932 +                    // Failed to read/skip the required amount, so fail
   4.933 +                    return -1;
   4.934 +                }
   4.935 +
   4.936 +                movement -= result;
   4.937 +            }
   4.938 +
   4.939 +        } else if (movement < 0) {
   4.940 +            // We can't seek backwards so we have to reopen the file and seek
   4.941 +            // forwards which obviously isn't very efficient
   4.942 +            Internal_Android_JNI_FileClose(ctx, false);
   4.943 +            Internal_Android_JNI_FileOpen(ctx);
   4.944 +            Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   4.945 +        }
   4.946 +    }
   4.947 +
   4.948 +    return ctx->hidden.androidio.position;
   4.949 +
   4.950 +}
   4.951 +
   4.952 +int Android_JNI_FileClose(SDL_RWops* ctx)
   4.953 +{
   4.954 +    return Internal_Android_JNI_FileClose(ctx, true);
   4.955 +}
   4.956 +
   4.957 +// returns a new global reference which needs to be released later
   4.958 +static jobject Android_JNI_GetSystemServiceObject(const char* name)
   4.959 +{
   4.960 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
   4.961 +    JNIEnv* env = Android_JNI_GetEnv();
   4.962 +    jobject retval = NULL;
   4.963 +
   4.964 +    if (!LocalReferenceHolder_Init(&refs, env)) {
   4.965 +        LocalReferenceHolder_Cleanup(&refs);
   4.966 +        return NULL;
   4.967 +    }
   4.968 +
   4.969 +    jstring service = (*env)->NewStringUTF(env, name);
   4.970 +
   4.971 +    jmethodID mid;
   4.972 +
   4.973 +    mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
   4.974 +    jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
   4.975 +
   4.976 +    mid = (*env)->GetMethodID(env, mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   4.977 +    jobject manager = (*env)->CallObjectMethod(env, context, mid, service);
   4.978 +
   4.979 +    (*env)->DeleteLocalRef(env, service);
   4.980 +
   4.981 +    retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL;
   4.982 +    LocalReferenceHolder_Cleanup(&refs);
   4.983 +    return retval;
   4.984 +}
   4.985 +
   4.986 +#define SETUP_CLIPBOARD(error) \
   4.987 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \
   4.988 +    JNIEnv* env = Android_JNI_GetEnv(); \
   4.989 +    if (!LocalReferenceHolder_Init(&refs, env)) { \
   4.990 +        LocalReferenceHolder_Cleanup(&refs); \
   4.991 +        return error; \
   4.992 +    } \
   4.993 +    jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   4.994 +    if (!clipboard) { \
   4.995 +        LocalReferenceHolder_Cleanup(&refs); \
   4.996 +        return error; \
   4.997 +    }
   4.998 +
   4.999 +#define CLEANUP_CLIPBOARD() \
  4.1000 +    LocalReferenceHolder_Cleanup(&refs);
  4.1001 +
  4.1002 +int Android_JNI_SetClipboardText(const char* text)
  4.1003 +{
  4.1004 +    SETUP_CLIPBOARD(-1)
  4.1005 +
  4.1006 +    jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V");
  4.1007 +    jstring string = (*env)->NewStringUTF(env, text);
  4.1008 +    (*env)->CallVoidMethod(env, clipboard, mid, string);
  4.1009 +    (*env)->DeleteGlobalRef(env, clipboard);
  4.1010 +    (*env)->DeleteLocalRef(env, string);
  4.1011 +
  4.1012 +    CLEANUP_CLIPBOARD();
  4.1013 +
  4.1014 +    return 0;
  4.1015 +}
  4.1016 +
  4.1017 +char* Android_JNI_GetClipboardText()
  4.1018 +{
  4.1019 +    SETUP_CLIPBOARD(SDL_strdup(""))
  4.1020 +
  4.1021 +    jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;");
  4.1022 +    jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid);
  4.1023 +    (*env)->DeleteGlobalRef(env, clipboard);
  4.1024 +    if (sequence) {
  4.1025 +        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;");
  4.1026 +        jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid));
  4.1027 +        const char* utf = (*env)->GetStringUTFChars(env, string, 0);
  4.1028 +        if (utf) {
  4.1029 +            char* text = SDL_strdup(utf);
  4.1030 +            (*env)->ReleaseStringUTFChars(env, string, utf);
  4.1031 +
  4.1032 +            CLEANUP_CLIPBOARD();
  4.1033 +
  4.1034 +            return text;
  4.1035 +        }
  4.1036 +    }
  4.1037 +
  4.1038 +    CLEANUP_CLIPBOARD();    
  4.1039 +
  4.1040 +    return SDL_strdup("");
  4.1041 +}
  4.1042 +
  4.1043 +SDL_bool Android_JNI_HasClipboardText()
  4.1044 +{
  4.1045 +    SETUP_CLIPBOARD(SDL_FALSE)
  4.1046 +
  4.1047 +    jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z");
  4.1048 +    jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid);
  4.1049 +    (*env)->DeleteGlobalRef(env, clipboard);
  4.1050 +
  4.1051 +    CLEANUP_CLIPBOARD();
  4.1052 +    
  4.1053 +    return has ? SDL_TRUE : SDL_FALSE;
  4.1054 +}
  4.1055 +
  4.1056 +
  4.1057 +// returns 0 on success or -1 on error (others undefined then)
  4.1058 +// returns truthy or falsy value in plugged, charged and battery
  4.1059 +// returns the value in seconds and percent or -1 if not available
  4.1060 +int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  4.1061 +{
  4.1062 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  4.1063 +    JNIEnv* env = Android_JNI_GetEnv();
  4.1064 +    if (!LocalReferenceHolder_Init(&refs, env)) {
  4.1065 +        LocalReferenceHolder_Cleanup(&refs);
  4.1066 +        return -1;
  4.1067 +    }
  4.1068 +
  4.1069 +    jmethodID mid;
  4.1070 +
  4.1071 +    mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
  4.1072 +    jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  4.1073 +
  4.1074 +    jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
  4.1075 +
  4.1076 +    jclass cls = (*env)->FindClass(env, "android/content/IntentFilter");
  4.1077 +
  4.1078 +    mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
  4.1079 +    jobject filter = (*env)->NewObject(env, cls, mid, action);
  4.1080 +
  4.1081 +    (*env)->DeleteLocalRef(env, action);
  4.1082 +
  4.1083 +    mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  4.1084 +    jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
  4.1085 +
  4.1086 +    (*env)->DeleteLocalRef(env, filter);
  4.1087 +
  4.1088 +    cls = (*env)->GetObjectClass(env, intent);
  4.1089 +
  4.1090 +    jstring iname;
  4.1091 +    jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
  4.1092 +
  4.1093 +#define GET_INT_EXTRA(var, key) \
  4.1094 +    iname = (*env)->NewStringUTF(env, key); \
  4.1095 +    int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
  4.1096 +    (*env)->DeleteLocalRef(env, iname);
  4.1097 +
  4.1098 +    jstring bname;
  4.1099 +    jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  4.1100 +
  4.1101 +#define GET_BOOL_EXTRA(var, key) \
  4.1102 +    bname = (*env)->NewStringUTF(env, key); \
  4.1103 +    int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
  4.1104 +    (*env)->DeleteLocalRef(env, bname);
  4.1105 +
  4.1106 +    if (plugged) {
  4.1107 +        GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  4.1108 +        if (plug == -1) {
  4.1109 +            LocalReferenceHolder_Cleanup(&refs);
  4.1110 +            return -1;
  4.1111 +        }
  4.1112 +        // 1 == BatteryManager.BATTERY_PLUGGED_AC
  4.1113 +        // 2 == BatteryManager.BATTERY_PLUGGED_USB
  4.1114 +        *plugged = (0 < plug) ? 1 : 0;
  4.1115 +    }
  4.1116 +
  4.1117 +    if (charged) {
  4.1118 +        GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  4.1119 +        if (status == -1) {
  4.1120 +            LocalReferenceHolder_Cleanup(&refs);
  4.1121 +            return -1;
  4.1122 +        }
  4.1123 +        // 5 == BatteryManager.BATTERY_STATUS_FULL
  4.1124 +        *charged = (status == 5) ? 1 : 0;
  4.1125 +    }
  4.1126 +
  4.1127 +    if (battery) {
  4.1128 +        GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  4.1129 +        *battery = present ? 1 : 0;
  4.1130 +    }
  4.1131 +
  4.1132 +    if (seconds) {
  4.1133 +        *seconds = -1; // not possible
  4.1134 +    }
  4.1135 +
  4.1136 +    if (percent) {
  4.1137 +        GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  4.1138 +        GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  4.1139 +        if ((level == -1) || (scale == -1)) {
  4.1140 +            LocalReferenceHolder_Cleanup(&refs);
  4.1141 +            return -1;
  4.1142 +        }
  4.1143 +        *percent = level * 100 / scale;
  4.1144 +    }
  4.1145 +
  4.1146 +    (*env)->DeleteLocalRef(env, intent);
  4.1147 +
  4.1148 +    LocalReferenceHolder_Cleanup(&refs);
  4.1149 +    return 0;
  4.1150 +}
  4.1151 +
  4.1152 +// sends message to be handled on the UI event dispatch thread
  4.1153 +int Android_JNI_SendMessage(int command, int param)
  4.1154 +{
  4.1155 +    JNIEnv *env = Android_JNI_GetEnv();
  4.1156 +    if (!env) {
  4.1157 +        return -1;
  4.1158 +    }
  4.1159 +    jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
  4.1160 +    if (!mid) {
  4.1161 +        return -1;
  4.1162 +    }
  4.1163 +    jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param);
  4.1164 +    return success ? 0 : -1;
  4.1165 +}
  4.1166 +
  4.1167 +void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  4.1168 +{
  4.1169 +    JNIEnv *env = Android_JNI_GetEnv();
  4.1170 +    if (!env) {
  4.1171 +        return;
  4.1172 +    }
  4.1173 +
  4.1174 +    jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z");
  4.1175 +    if (!mid) {
  4.1176 +        return;
  4.1177 +    }
  4.1178 +    (*env)->CallStaticBooleanMethod(env, mActivityClass, mid,
  4.1179 +                               inputRect->x,
  4.1180 +                               inputRect->y,
  4.1181 +                               inputRect->w,
  4.1182 +                               inputRect->h );
  4.1183 +}
  4.1184 +
  4.1185 +void Android_JNI_HideTextInput()
  4.1186 +{
  4.1187 +    // has to match Activity constant
  4.1188 +    const int COMMAND_TEXTEDIT_HIDE = 3;
  4.1189 +    Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  4.1190 +}
  4.1191 +
  4.1192 +//////////////////////////////////////////////////////////////////////////////
  4.1193 +//
  4.1194 +// Functions exposed to SDL applications in SDL_system.h
  4.1195 +//
  4.1196 +
  4.1197 +void *SDL_AndroidGetJNIEnv()
  4.1198 +{
  4.1199 +    return Android_JNI_GetEnv();
  4.1200 +}
  4.1201 +
  4.1202 +
  4.1203 +
  4.1204 +void *SDL_AndroidGetActivity()
  4.1205 +{
  4.1206 +    /* See SDL_system.h for caveats on using this function. */
  4.1207 +
  4.1208 +    jmethodID mid;
  4.1209 +
  4.1210 +    JNIEnv *env = Android_JNI_GetEnv();
  4.1211 +    if (!env) {
  4.1212 +        return NULL;
  4.1213 +    }
  4.1214 +
  4.1215 +    // return SDLActivity.getContext();
  4.1216 +    mid = (*env)->GetStaticMethodID(env, mActivityClass,
  4.1217 +            "getContext","()Landroid/content/Context;");
  4.1218 +    return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  4.1219 +}
  4.1220 +
  4.1221 +const char * SDL_AndroidGetInternalStoragePath()
  4.1222 +{
  4.1223 +    static char *s_AndroidInternalFilesPath = NULL;
  4.1224 +
  4.1225 +    if (!s_AndroidInternalFilesPath) {
  4.1226 +        struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  4.1227 +        jmethodID mid;
  4.1228 +        jobject context;
  4.1229 +        jobject fileObject;
  4.1230 +        jstring pathString;
  4.1231 +        const char *path;
  4.1232 +
  4.1233 +        JNIEnv *env = Android_JNI_GetEnv();
  4.1234 +        if (!LocalReferenceHolder_Init(&refs, env)) {
  4.1235 +            LocalReferenceHolder_Cleanup(&refs);
  4.1236 +            return NULL;
  4.1237 +        }
  4.1238 +
  4.1239 +        // context = SDLActivity.getContext();
  4.1240 +        mid = (*env)->GetStaticMethodID(env, mActivityClass,
  4.1241 +                "getContext","()Landroid/content/Context;");
  4.1242 +        context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  4.1243 +
  4.1244 +        // fileObj = context.getFilesDir();
  4.1245 +        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  4.1246 +                "getFilesDir", "()Ljava/io/File;");
  4.1247 +        fileObject = (*env)->CallObjectMethod(env, context, mid);
  4.1248 +        if (!fileObject) {
  4.1249 +            SDL_SetError("Couldn't get internal directory");
  4.1250 +            LocalReferenceHolder_Cleanup(&refs);
  4.1251 +            return NULL;
  4.1252 +        }
  4.1253 +
  4.1254 +        // path = fileObject.getAbsolutePath();
  4.1255 +        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  4.1256 +                "getAbsolutePath", "()Ljava/lang/String;");
  4.1257 +        pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  4.1258 +
  4.1259 +        path = (*env)->GetStringUTFChars(env, pathString, NULL);
  4.1260 +        s_AndroidInternalFilesPath = SDL_strdup(path);
  4.1261 +        (*env)->ReleaseStringUTFChars(env, pathString, path);
  4.1262 +
  4.1263 +        LocalReferenceHolder_Cleanup(&refs);
  4.1264 +    }
  4.1265 +    return s_AndroidInternalFilesPath;
  4.1266 +}
  4.1267 +
  4.1268 +int SDL_AndroidGetExternalStorageState()
  4.1269 +{
  4.1270 +    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  4.1271 +    jmethodID mid;
  4.1272 +    jclass cls;
  4.1273 +    jstring stateString;
  4.1274 +    const char *state;
  4.1275 +    int stateFlags;
  4.1276 +
  4.1277 +    JNIEnv *env = Android_JNI_GetEnv();
  4.1278 +    if (!LocalReferenceHolder_Init(&refs, env)) {
  4.1279 +        LocalReferenceHolder_Cleanup(&refs);
  4.1280 +        return 0;
  4.1281 +    }
  4.1282 +
  4.1283 +    cls = (*env)->FindClass(env, "android/os/Environment");
  4.1284 +    mid = (*env)->GetStaticMethodID(env, cls,
  4.1285 +            "getExternalStorageState", "()Ljava/lang/String;");
  4.1286 +    stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
  4.1287 +
  4.1288 +    state = (*env)->GetStringUTFChars(env, stateString, NULL);
  4.1289 +
  4.1290 +    // Print an info message so people debugging know the storage state
  4.1291 +    __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  4.1292 +
  4.1293 +    if (SDL_strcmp(state, "mounted") == 0) {
  4.1294 +        stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  4.1295 +                     SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  4.1296 +    } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  4.1297 +        stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  4.1298 +    } else {
  4.1299 +        stateFlags = 0;
  4.1300 +    }
  4.1301 +    (*env)->ReleaseStringUTFChars(env, stateString, state);
  4.1302 +
  4.1303 +    LocalReferenceHolder_Cleanup(&refs);
  4.1304 +    return stateFlags;
  4.1305 +}
  4.1306 +
  4.1307 +const char * SDL_AndroidGetExternalStoragePath()
  4.1308 +{
  4.1309 +    static char *s_AndroidExternalFilesPath = NULL;
  4.1310 +
  4.1311 +    if (!s_AndroidExternalFilesPath) {
  4.1312 +        struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
  4.1313 +        jmethodID mid;
  4.1314 +        jobject context;
  4.1315 +        jobject fileObject;
  4.1316 +        jstring pathString;
  4.1317 +        const char *path;
  4.1318 +
  4.1319 +        JNIEnv *env = Android_JNI_GetEnv();
  4.1320 +        if (!LocalReferenceHolder_Init(&refs, env)) {
  4.1321 +            LocalReferenceHolder_Cleanup(&refs);
  4.1322 +            return NULL;
  4.1323 +        }
  4.1324 +
  4.1325 +        // context = SDLActivity.getContext();
  4.1326 +        mid = (*env)->GetStaticMethodID(env, mActivityClass,
  4.1327 +                "getContext","()Landroid/content/Context;");
  4.1328 +        context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
  4.1329 +
  4.1330 +        // fileObj = context.getExternalFilesDir();
  4.1331 +        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
  4.1332 +                "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  4.1333 +        fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
  4.1334 +        if (!fileObject) {
  4.1335 +            SDL_SetError("Couldn't get external directory");
  4.1336 +            LocalReferenceHolder_Cleanup(&refs);
  4.1337 +            return NULL;
  4.1338 +        }
  4.1339 +
  4.1340 +        // path = fileObject.getAbsolutePath();
  4.1341 +        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
  4.1342 +                "getAbsolutePath", "()Ljava/lang/String;");
  4.1343 +        pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
  4.1344 +
  4.1345 +        path = (*env)->GetStringUTFChars(env, pathString, NULL);
  4.1346 +        s_AndroidExternalFilesPath = SDL_strdup(path);
  4.1347 +        (*env)->ReleaseStringUTFChars(env, pathString, path);
  4.1348 +
  4.1349 +        LocalReferenceHolder_Cleanup(&refs);
  4.1350 +    }
  4.1351 +    return s_AndroidExternalFilesPath;
  4.1352 +}
  4.1353 +
  4.1354 +#endif /* __ANDROID__ */
  4.1355 +
  4.1356 +/* vi: set ts=4 sw=4 expandtab: */
     5.1 --- a/src/core/android/SDL_android.cpp	Mon Jul 22 22:54:00 2013 +0200
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,1295 +0,0 @@
     5.4 -/*
     5.5 -  Simple DirectMedia Layer
     5.6 -  Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
     5.7 -
     5.8 -  This software is provided 'as-is', without any express or implied
     5.9 -  warranty.  In no event will the authors be held liable for any damages
    5.10 -  arising from the use of this software.
    5.11 -
    5.12 -  Permission is granted to anyone to use this software for any purpose,
    5.13 -  including commercial applications, and to alter it and redistribute it
    5.14 -  freely, subject to the following restrictions:
    5.15 -
    5.16 -  1. The origin of this software must not be misrepresented; you must not
    5.17 -     claim that you wrote the original software. If you use this software
    5.18 -     in a product, an acknowledgment in the product documentation would be
    5.19 -     appreciated but is not required.
    5.20 -  2. Altered source versions must be plainly marked as such, and must not be
    5.21 -     misrepresented as being the original software.
    5.22 -  3. This notice may not be removed or altered from any source distribution.
    5.23 -*/
    5.24 -#include "SDL_config.h"
    5.25 -#include "SDL_stdinc.h"
    5.26 -#include "SDL_assert.h"
    5.27 -#include "SDL_log.h"
    5.28 -
    5.29 -#ifdef __ANDROID__
    5.30 -
    5.31 -#include "SDL_system.h"
    5.32 -#include "SDL_android.h"
    5.33 -#include <EGL/egl.h>
    5.34 -
    5.35 -extern "C" {
    5.36 -#include "../../events/SDL_events_c.h"
    5.37 -#include "../../video/android/SDL_androidkeyboard.h"
    5.38 -#include "../../video/android/SDL_androidtouch.h"
    5.39 -#include "../../video/android/SDL_androidvideo.h"
    5.40 -
    5.41 -#include <android/log.h>
    5.42 -#include <pthread.h>
    5.43 -#include <sys/types.h>
    5.44 -#include <unistd.h>
    5.45 -#define LOG_TAG "SDL_android"
    5.46 -//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
    5.47 -//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
    5.48 -#define LOGI(...) do {} while (false)
    5.49 -#define LOGE(...) do {} while (false)
    5.50 -
    5.51 -/* Uncomment this to log messages entering and exiting methods in this file */
    5.52 -//#define DEBUG_JNI
    5.53 -
    5.54 -/* Implemented in audio/android/SDL_androidaudio.c */
    5.55 -extern void Android_RunAudioThread();
    5.56 -
    5.57 -static void Android_JNI_ThreadDestroyed(void*);
    5.58 -} // C
    5.59 -
    5.60 -/*******************************************************************************
    5.61 - This file links the Java side of Android with libsdl
    5.62 -*******************************************************************************/
    5.63 -#include <jni.h>
    5.64 -#include <android/log.h>
    5.65 -
    5.66 -
    5.67 -/*******************************************************************************
    5.68 -                               Globals
    5.69 -*******************************************************************************/
    5.70 -static pthread_key_t mThreadKey;
    5.71 -static JavaVM* mJavaVM;
    5.72 -
    5.73 -// Main activity
    5.74 -static jclass mActivityClass;
    5.75 -
    5.76 -// method signatures
    5.77 -static jmethodID midCreateGLContext;
    5.78 -static jmethodID midFlipBuffers;
    5.79 -static jmethodID midAudioInit;
    5.80 -static jmethodID midAudioWriteShortBuffer;
    5.81 -static jmethodID midAudioWriteByteBuffer;
    5.82 -static jmethodID midAudioQuit;
    5.83 -
    5.84 -// Accelerometer data storage
    5.85 -static float fLastAccelerometer[3];
    5.86 -static bool bHasNewData;
    5.87 -
    5.88 -/*******************************************************************************
    5.89 -                 Functions called by JNI
    5.90 -*******************************************************************************/
    5.91 -
    5.92 -// Library init
    5.93 -extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    5.94 -{
    5.95 -    JNIEnv *env;
    5.96 -    mJavaVM = vm;
    5.97 -    LOGI("JNI_OnLoad called");
    5.98 -    if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
    5.99 -        LOGE("Failed to get the environment using GetEnv()");
   5.100 -        return -1;
   5.101 -    }
   5.102 -    /*
   5.103 -     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
   5.104 -     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
   5.105 -     */
   5.106 -    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
   5.107 -        __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
   5.108 -    }
   5.109 -    else {
   5.110 -        Android_JNI_SetupThread();
   5.111 -    }
   5.112 -
   5.113 -    return JNI_VERSION_1_4;
   5.114 -}
   5.115 -
   5.116 -// Called before SDL_main() to initialize JNI bindings
   5.117 -extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
   5.118 -{
   5.119 -    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
   5.120 -
   5.121 -    Android_JNI_SetupThread();
   5.122 -
   5.123 -    mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
   5.124 -
   5.125 -    midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
   5.126 -                                "createGLContext","(II[I)Z");
   5.127 -    midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
   5.128 -                                "flipBuffers","()V");
   5.129 -    midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
   5.130 -                                "audioInit", "(IZZI)V");
   5.131 -    midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
   5.132 -                                "audioWriteShortBuffer", "([S)V");
   5.133 -    midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
   5.134 -                                "audioWriteByteBuffer", "([B)V");
   5.135 -    midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
   5.136 -                                "audioQuit", "()V");
   5.137 -
   5.138 -    bHasNewData = false;
   5.139 -
   5.140 -    if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
   5.141 -       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
   5.142 -        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
   5.143 -    }
   5.144 -    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
   5.145 -}
   5.146 -
   5.147 -// Resize
   5.148 -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
   5.149 -                                    JNIEnv* env, jclass jcls,
   5.150 -                                    jint width, jint height, jint format)
   5.151 -{
   5.152 -    Android_SetScreenResolution(width, height, format);
   5.153 -}
   5.154 -
   5.155 -// Keydown
   5.156 -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
   5.157 -                                    JNIEnv* env, jclass jcls, jint keycode)
   5.158 -{
   5.159 -    Android_OnKeyDown(keycode);
   5.160 -}
   5.161 -
   5.162 -// Keyup
   5.163 -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
   5.164 -                                    JNIEnv* env, jclass jcls, jint keycode)
   5.165 -{
   5.166 -    Android_OnKeyUp(keycode);
   5.167 -}
   5.168 -
   5.169 -// Touch
   5.170 -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
   5.171 -                                    JNIEnv* env, jclass jcls,
   5.172 -                                    jint touch_device_id_in, jint pointer_finger_id_in,
   5.173 -                                    jint action, jfloat x, jfloat y, jfloat p)
   5.174 -{
   5.175 -    Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
   5.176 -}
   5.177 -
   5.178 -// Accelerometer
   5.179 -extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
   5.180 -                                    JNIEnv* env, jclass jcls,
   5.181 -                                    jfloat x, jfloat y, jfloat z)
   5.182 -{
   5.183 -    fLastAccelerometer[0] = x;
   5.184 -    fLastAccelerometer[1] = y;
   5.185 -    fLastAccelerometer[2] = z;
   5.186 -    bHasNewData = true;
   5.187 -}
   5.188 -
   5.189 -// Low memory
   5.190 -extern "C" void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
   5.191 -                                    JNIEnv* env, jclass cls)
   5.192 -{
   5.193 -    SDL_SendAppEvent(SDL_APP_LOWMEMORY);
   5.194 -}
   5.195 -
   5.196 -// Quit
   5.197 -extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
   5.198 -                                    JNIEnv* env, jclass cls)
   5.199 -{
   5.200 -    // Inject a SDL_QUIT event
   5.201 -    SDL_SendQuit();
   5.202 -    SDL_SendAppEvent(SDL_APP_TERMINATING);
   5.203 -}
   5.204 -
   5.205 -// Pause
   5.206 -extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
   5.207 -                                    JNIEnv* env, jclass cls)
   5.208 -{
   5.209 -    if (Android_Window) {
   5.210 -        /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
   5.211 -        if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
   5.212 -        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
   5.213 -        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
   5.214 -    }
   5.215 -
   5.216 -    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
   5.217 -    SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
   5.218 -    SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
   5.219 -}
   5.220 -
   5.221 -// Resume
   5.222 -extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
   5.223 -                                    JNIEnv* env, jclass cls)
   5.224 -{
   5.225 -    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
   5.226 -    SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
   5.227 -    SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
   5.228 -
   5.229 -    if (Android_Window) {
   5.230 -        /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
   5.231 -         * We can't restore the GL Context here because it needs to be done on the SDL main thread
   5.232 -         * and this function will be called from the Java thread instead.
   5.233 -         */
   5.234 -        if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
   5.235 -        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
   5.236 -        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
   5.237 -    }
   5.238 -}
   5.239 -
   5.240 -extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
   5.241 -                                    JNIEnv* env, jclass cls)
   5.242 -{
   5.243 -    /* This is the audio thread, with a different environment */
   5.244 -    Android_JNI_SetupThread();
   5.245 -
   5.246 -    Android_RunAudioThread();
   5.247 -}
   5.248 -
   5.249 -extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
   5.250 -                                    JNIEnv* env, jclass cls,
   5.251 -                                    jstring text, jint newCursorPosition)
   5.252 -{
   5.253 -    const char *utftext = env->GetStringUTFChars(text, NULL);
   5.254 -
   5.255 -    SDL_SendKeyboardText(utftext);
   5.256 -
   5.257 -    env->ReleaseStringUTFChars(text, utftext);
   5.258 -}
   5.259 -
   5.260 -extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
   5.261 -                                    JNIEnv* env, jclass cls,
   5.262 -                                    jstring text, jint newCursorPosition)
   5.263 -{
   5.264 -    const char *utftext = env->GetStringUTFChars(text, NULL);
   5.265 -
   5.266 -    SDL_SendEditingText(utftext, 0, 0);
   5.267 -
   5.268 -    env->ReleaseStringUTFChars(text, utftext);
   5.269 -}
   5.270 -
   5.271 -
   5.272 -
   5.273 -/*******************************************************************************
   5.274 -             Functions called by SDL into Java
   5.275 -*******************************************************************************/
   5.276 -
   5.277 -class LocalReferenceHolder
   5.278 -{
   5.279 -private:
   5.280 -    static int s_active;
   5.281 -
   5.282 -public:
   5.283 -    static bool IsActive() {
   5.284 -        return s_active > 0;
   5.285 -    }
   5.286 -
   5.287 -public:
   5.288 -    LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
   5.289 -#ifdef DEBUG_JNI
   5.290 -        SDL_Log("Entering function %s", m_func);
   5.291 -#endif
   5.292 -    }
   5.293 -    ~LocalReferenceHolder() {
   5.294 -#ifdef DEBUG_JNI
   5.295 -        SDL_Log("Leaving function %s", m_func);
   5.296 -#endif
   5.297 -        if (m_env) {
   5.298 -            m_env->PopLocalFrame(NULL);
   5.299 -            --s_active;
   5.300 -        }
   5.301 -    }
   5.302 -
   5.303 -    bool init(JNIEnv *env, jint capacity = 16) {
   5.304 -        if (env->PushLocalFrame(capacity) < 0) {
   5.305 -            SDL_SetError("Failed to allocate enough JVM local references");
   5.306 -            return false;
   5.307 -        }
   5.308 -        ++s_active;
   5.309 -        m_env = env;
   5.310 -        return true;
   5.311 -    }
   5.312 -
   5.313 -protected:
   5.314 -    JNIEnv *m_env;
   5.315 -    const char *m_func;
   5.316 -};
   5.317 -int LocalReferenceHolder::s_active;
   5.318 -
   5.319 -extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
   5.320 -                                int red, int green, int blue, int alpha,
   5.321 -                                int buffer, int depth, int stencil,
   5.322 -                                int buffers, int samples)
   5.323 -{
   5.324 -    JNIEnv *env = Android_JNI_GetEnv();
   5.325 -
   5.326 -    jint attribs[] = {
   5.327 -        EGL_RED_SIZE, red,
   5.328 -        EGL_GREEN_SIZE, green,
   5.329 -        EGL_BLUE_SIZE, blue,
   5.330 -        EGL_ALPHA_SIZE, alpha,
   5.331 -        EGL_BUFFER_SIZE, buffer,
   5.332 -        EGL_DEPTH_SIZE, depth,
   5.333 -        EGL_STENCIL_SIZE, stencil,
   5.334 -        EGL_SAMPLE_BUFFERS, buffers,
   5.335 -        EGL_SAMPLES, samples,
   5.336 -        EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
   5.337 -        EGL_NONE
   5.338 -    };
   5.339 -    int len = SDL_arraysize(attribs);
   5.340 -
   5.341 -    jintArray array;
   5.342 -
   5.343 -    array = env->NewIntArray(len);
   5.344 -    env->SetIntArrayRegion(array, 0, len, attribs);
   5.345 -
   5.346 -    jboolean success = env->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
   5.347 -
   5.348 -    env->DeleteLocalRef(array);
   5.349 -
   5.350 -    return success ? SDL_TRUE : SDL_FALSE;
   5.351 -}
   5.352 -
   5.353 -extern "C" void Android_JNI_SwapWindow()
   5.354 -{
   5.355 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.356 -    mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
   5.357 -}
   5.358 -
   5.359 -extern "C" void Android_JNI_SetActivityTitle(const char *title)
   5.360 -{
   5.361 -    jmethodID mid;
   5.362 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.363 -    mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
   5.364 -    if (mid) {
   5.365 -        jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
   5.366 -        mEnv->CallStaticBooleanMethod(mActivityClass, mid, jtitle);
   5.367 -        mEnv->DeleteLocalRef(jtitle);
   5.368 -    }
   5.369 -}
   5.370 -
   5.371 -extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
   5.372 -{
   5.373 -    int i;
   5.374 -    SDL_bool retval = SDL_FALSE;
   5.375 -
   5.376 -    if (bHasNewData) {
   5.377 -        for (i = 0; i < 3; ++i) {
   5.378 -            values[i] = fLastAccelerometer[i];
   5.379 -        }
   5.380 -        bHasNewData = false;
   5.381 -        retval = SDL_TRUE;
   5.382 -    }
   5.383 -
   5.384 -    return retval;
   5.385 -}
   5.386 -
   5.387 -static void Android_JNI_ThreadDestroyed(void* value) {
   5.388 -    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
   5.389 -    JNIEnv *env = (JNIEnv*) value;
   5.390 -    if (env != NULL) {
   5.391 -        mJavaVM->DetachCurrentThread();
   5.392 -        pthread_setspecific(mThreadKey, NULL);
   5.393 -    }
   5.394 -}
   5.395 -
   5.396 -JNIEnv* Android_JNI_GetEnv(void) {
   5.397 -    /* From http://developer.android.com/guide/practices/jni.html
   5.398 -     * All threads are Linux threads, scheduled by the kernel.
   5.399 -     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
   5.400 -     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
   5.401 -     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
   5.402 -     * and cannot make JNI calls.
   5.403 -     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
   5.404 -     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
   5.405 -     * is a no-op.
   5.406 -     * Note: You can call this function any number of times for the same thread, there's no harm in it
   5.407 -     */
   5.408 -
   5.409 -    JNIEnv *env;
   5.410 -    int status = mJavaVM->AttachCurrentThread(&env, NULL);
   5.411 -    if(status < 0) {
   5.412 -        LOGE("failed to attach current thread");
   5.413 -        return 0;
   5.414 -    }
   5.415 -
   5.416 -    return env;
   5.417 -}
   5.418 -
   5.419 -int Android_JNI_SetupThread(void) {
   5.420 -    /* From http://developer.android.com/guide/practices/jni.html
   5.421 -     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
   5.422 -     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
   5.423 -     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
   5.424 -     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
   5.425 -     * Note: The destructor is not called unless the stored value is != NULL
   5.426 -     * Note: You can call this function any number of times for the same thread, there's no harm in it
   5.427 -     *       (except for some lost CPU cycles)
   5.428 -     */
   5.429 -    JNIEnv *env = Android_JNI_GetEnv();
   5.430 -    pthread_setspecific(mThreadKey, (void*) env);
   5.431 -    return 1;
   5.432 -}
   5.433 -
   5.434 -//
   5.435 -// Audio support
   5.436 -//
   5.437 -static jboolean audioBuffer16Bit = JNI_FALSE;
   5.438 -static jboolean audioBufferStereo = JNI_FALSE;
   5.439 -static jobject audioBuffer = NULL;
   5.440 -static void* audioBufferPinned = NULL;
   5.441 -
   5.442 -extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
   5.443 -{
   5.444 -    int audioBufferFrames;
   5.445 -
   5.446 -    JNIEnv *env = Android_JNI_GetEnv();
   5.447 -
   5.448 -    if (!env) {
   5.449 -        LOGE("callback_handler: failed to attach current thread");
   5.450 -    }
   5.451 -    Android_JNI_SetupThread();
   5.452 -
   5.453 -    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
   5.454 -    audioBuffer16Bit = is16Bit;
   5.455 -    audioBufferStereo = channelCount > 1;
   5.456 -
   5.457 -    env->CallStaticVoidMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
   5.458 -
   5.459 -    /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
   5.460 -     * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
   5.461 -
   5.462 -    if (is16Bit) {
   5.463 -        jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   5.464 -        if (audioBufferLocal) {
   5.465 -            audioBuffer = env->NewGlobalRef(audioBufferLocal);
   5.466 -            env->DeleteLocalRef(audioBufferLocal);
   5.467 -        }
   5.468 -    }
   5.469 -    else {
   5.470 -        jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
   5.471 -        if (audioBufferLocal) {
   5.472 -            audioBuffer = env->NewGlobalRef(audioBufferLocal);
   5.473 -            env->DeleteLocalRef(audioBufferLocal);
   5.474 -        }
   5.475 -    }
   5.476 -
   5.477 -    if (audioBuffer == NULL) {
   5.478 -        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
   5.479 -        return 0;
   5.480 -    }
   5.481 -
   5.482 -    jboolean isCopy = JNI_FALSE;
   5.483 -    if (audioBuffer16Bit) {
   5.484 -        audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
   5.485 -        audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
   5.486 -    } else {
   5.487 -        audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
   5.488 -        audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
   5.489 -    }
   5.490 -    if (audioBufferStereo) {
   5.491 -        audioBufferFrames /= 2;
   5.492 -    }
   5.493 -
   5.494 -    return audioBufferFrames;
   5.495 -}
   5.496 -
   5.497 -extern "C" void * Android_JNI_GetAudioBuffer()
   5.498 -{
   5.499 -    return audioBufferPinned;
   5.500 -}
   5.501 -
   5.502 -extern "C" void Android_JNI_WriteAudioBuffer()
   5.503 -{
   5.504 -    JNIEnv *mAudioEnv = Android_JNI_GetEnv();
   5.505 -
   5.506 -    if (audioBuffer16Bit) {
   5.507 -        mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
   5.508 -        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
   5.509 -    } else {
   5.510 -        mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
   5.511 -        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
   5.512 -    }
   5.513 -
   5.514 -    /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
   5.515 -}
   5.516 -
   5.517 -extern "C" void Android_JNI_CloseAudioDevice()
   5.518 -{
   5.519 -    JNIEnv *env = Android_JNI_GetEnv();
   5.520 -
   5.521 -    env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
   5.522 -
   5.523 -    if (audioBuffer) {
   5.524 -        env->DeleteGlobalRef(audioBuffer);
   5.525 -        audioBuffer = NULL;
   5.526 -        audioBufferPinned = NULL;
   5.527 -    }
   5.528 -}
   5.529 -
   5.530 -// Test for an exception and call SDL_SetError with its detail if one occurs
   5.531 -// If optional parameter silent is truthy then SDL_SetError() is not called.
   5.532 -static bool Android_JNI_ExceptionOccurred(bool silent = false)
   5.533 -{
   5.534 -    SDL_assert(LocalReferenceHolder::IsActive());
   5.535 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.536 -
   5.537 -    jthrowable exception = mEnv->ExceptionOccurred();
   5.538 -    if (exception != NULL) {
   5.539 -        jmethodID mid;
   5.540 -
   5.541 -        // Until this happens most JNI operations have undefined behaviour
   5.542 -        mEnv->ExceptionClear();
   5.543 -
   5.544 -        if (!silent) {
   5.545 -            jclass exceptionClass = mEnv->GetObjectClass(exception);
   5.546 -            jclass classClass = mEnv->FindClass("java/lang/Class");
   5.547 -
   5.548 -            mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
   5.549 -            jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
   5.550 -            const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
   5.551 -
   5.552 -            mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
   5.553 -            jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
   5.554 -
   5.555 -            if (exceptionMessage != NULL) {
   5.556 -                const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(exceptionMessage, 0);
   5.557 -                SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
   5.558 -                mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
   5.559 -            } else {
   5.560 -                SDL_SetError("%s", exceptionNameUTF8);
   5.561 -            }
   5.562 -
   5.563 -            mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
   5.564 -        }
   5.565 -
   5.566 -        return true;
   5.567 -    }
   5.568 -
   5.569 -    return false;
   5.570 -}
   5.571 -
   5.572 -static int Android_JNI_FileOpen(SDL_RWops* ctx)
   5.573 -{
   5.574 -    LocalReferenceHolder refs(__FUNCTION__);
   5.575 -    int result = 0;
   5.576 -
   5.577 -    jmethodID mid;
   5.578 -    jobject context;
   5.579 -    jobject assetManager;
   5.580 -    jobject inputStream;
   5.581 -    jclass channels;
   5.582 -    jobject readableByteChannel;
   5.583 -    jstring fileNameJString;
   5.584 -    jobject fd;
   5.585 -    jclass fdCls;
   5.586 -    jfieldID descriptor;
   5.587 -
   5.588 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.589 -    if (!refs.init(mEnv)) {
   5.590 -        goto failure;
   5.591 -    }
   5.592 -
   5.593 -    fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
   5.594 -    ctx->hidden.androidio.position = 0;
   5.595 -
   5.596 -    // context = SDLActivity.getContext();
   5.597 -    mid = mEnv->GetStaticMethodID(mActivityClass,
   5.598 -            "getContext","()Landroid/content/Context;");
   5.599 -    context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
   5.600 -
   5.601 -
   5.602 -    // assetManager = context.getAssets();
   5.603 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
   5.604 -            "getAssets", "()Landroid/content/res/AssetManager;");
   5.605 -    assetManager = mEnv->CallObjectMethod(context, mid);
   5.606 -
   5.607 -    /* First let's try opening the file to obtain an AssetFileDescriptor.
   5.608 -    * This method reads the files directly from the APKs using standard *nix calls
   5.609 -    */
   5.610 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
   5.611 -    inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
   5.612 -    if (Android_JNI_ExceptionOccurred(true)) {
   5.613 -        goto fallback;
   5.614 -    }
   5.615 -
   5.616 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
   5.617 -    ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
   5.618 -    if (Android_JNI_ExceptionOccurred(true)) {
   5.619 -        goto fallback;
   5.620 -    }
   5.621 -
   5.622 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
   5.623 -    ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
   5.624 -    if (Android_JNI_ExceptionOccurred(true)) {
   5.625 -        goto fallback;
   5.626 -    }
   5.627 -
   5.628 -    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
   5.629 -    fd = mEnv->CallObjectMethod(inputStream, mid);
   5.630 -    fdCls = mEnv->GetObjectClass(fd);
   5.631 -    descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
   5.632 -    ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
   5.633 -    ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
   5.634 -
   5.635 -    // Seek to the correct offset in the file.
   5.636 -    lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
   5.637 -
   5.638 -    if (false) {
   5.639 -fallback:
   5.640 -        // Disabled log message because of spam on the Nexus 7
   5.641 -        //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
   5.642 -
   5.643 -        /* Try the old method using InputStream */
   5.644 -        ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   5.645 -
   5.646 -        // inputStream = assetManager.open(<filename>);
   5.647 -        mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
   5.648 -                "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
   5.649 -        inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
   5.650 -        if (Android_JNI_ExceptionOccurred()) {
   5.651 -            goto failure;
   5.652 -        }
   5.653 -
   5.654 -        ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
   5.655 -
   5.656 -        // Despite all the visible documentation on [Asset]InputStream claiming
   5.657 -        // that the .available() method is not guaranteed to return the entire file
   5.658 -        // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
   5.659 -        // android/apis/content/ReadAsset.java imply that Android's
   5.660 -        // AssetInputStream.available() /will/ always return the total file size
   5.661 -
   5.662 -        // size = inputStream.available();
   5.663 -        mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   5.664 -                "available", "()I");
   5.665 -        ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
   5.666 -        if (Android_JNI_ExceptionOccurred()) {
   5.667 -            goto failure;
   5.668 -        }
   5.669 -
   5.670 -        // readableByteChannel = Channels.newChannel(inputStream);
   5.671 -        channels = mEnv->FindClass("java/nio/channels/Channels");
   5.672 -        mid = mEnv->GetStaticMethodID(channels,
   5.673 -                "newChannel",
   5.674 -                "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
   5.675 -        readableByteChannel = mEnv->CallStaticObjectMethod(
   5.676 -                channels, mid, inputStream);
   5.677 -        if (Android_JNI_ExceptionOccurred()) {
   5.678 -            goto failure;
   5.679 -        }
   5.680 -
   5.681 -        ctx->hidden.androidio.readableByteChannelRef =
   5.682 -            mEnv->NewGlobalRef(readableByteChannel);
   5.683 -
   5.684 -        // Store .read id for reading purposes
   5.685 -        mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
   5.686 -                "read", "(Ljava/nio/ByteBuffer;)I");
   5.687 -        ctx->hidden.androidio.readMethod = mid;
   5.688 -    }
   5.689 -
   5.690 -    if (false) {
   5.691 -failure:
   5.692 -        result = -1;
   5.693 -
   5.694 -        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   5.695 -
   5.696 -        if(ctx->hidden.androidio.inputStreamRef != NULL) {
   5.697 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   5.698 -        }
   5.699 -
   5.700 -        if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
   5.701 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   5.702 -        }
   5.703 -
   5.704 -        if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
   5.705 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   5.706 -        }
   5.707 -
   5.708 -    }
   5.709 -
   5.710 -    return result;
   5.711 -}
   5.712 -
   5.713 -extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
   5.714 -        const char* fileName, const char*)
   5.715 -{
   5.716 -    LocalReferenceHolder refs(__FUNCTION__);
   5.717 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.718 -
   5.719 -    if (!refs.init(mEnv)) {
   5.720 -        return -1;
   5.721 -    }
   5.722 -
   5.723 -    if (!ctx) {
   5.724 -        return -1;
   5.725 -    }
   5.726 -
   5.727 -    jstring fileNameJString = mEnv->NewStringUTF(fileName);
   5.728 -    ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
   5.729 -    ctx->hidden.androidio.inputStreamRef = NULL;
   5.730 -    ctx->hidden.androidio.readableByteChannelRef = NULL;
   5.731 -    ctx->hidden.androidio.readMethod = NULL;
   5.732 -    ctx->hidden.androidio.assetFileDescriptorRef = NULL;
   5.733 -
   5.734 -    return Android_JNI_FileOpen(ctx);
   5.735 -}
   5.736 -
   5.737 -extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
   5.738 -        size_t size, size_t maxnum)
   5.739 -{
   5.740 -    LocalReferenceHolder refs(__FUNCTION__);
   5.741 -
   5.742 -    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   5.743 -        size_t bytesMax = size * maxnum;
   5.744 -        if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
   5.745 -            bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
   5.746 -        }
   5.747 -        size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
   5.748 -        if (result > 0) {
   5.749 -            ctx->hidden.androidio.position += result;
   5.750 -            return result / size;
   5.751 -        }
   5.752 -        return 0;
   5.753 -    } else {
   5.754 -        jlong bytesRemaining = (jlong) (size * maxnum);
   5.755 -        jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
   5.756 -        int bytesRead = 0;
   5.757 -
   5.758 -        /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
   5.759 -        if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
   5.760 -
   5.761 -        JNIEnv *mEnv = Android_JNI_GetEnv();
   5.762 -        if (!refs.init(mEnv)) {
   5.763 -            return 0;
   5.764 -        }
   5.765 -
   5.766 -        jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
   5.767 -        jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
   5.768 -        jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
   5.769 -
   5.770 -        while (bytesRemaining > 0) {
   5.771 -            // result = readableByteChannel.read(...);
   5.772 -            int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
   5.773 -
   5.774 -            if (Android_JNI_ExceptionOccurred()) {
   5.775 -                return 0;
   5.776 -            }
   5.777 -
   5.778 -            if (result < 0) {
   5.779 -                break;
   5.780 -            }
   5.781 -
   5.782 -            bytesRemaining -= result;
   5.783 -            bytesRead += result;
   5.784 -            ctx->hidden.androidio.position += result;
   5.785 -        }
   5.786 -        return bytesRead / size;
   5.787 -    }
   5.788 -}
   5.789 -
   5.790 -extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
   5.791 -        size_t size, size_t num)
   5.792 -{
   5.793 -    SDL_SetError("Cannot write to Android package filesystem");
   5.794 -    return 0;
   5.795 -}
   5.796 -
   5.797 -static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
   5.798 -{
   5.799 -    LocalReferenceHolder refs(__FUNCTION__);
   5.800 -    int result = 0;
   5.801 -    JNIEnv *mEnv = Android_JNI_GetEnv();
   5.802 -
   5.803 -    if (!refs.init(mEnv)) {
   5.804 -        return SDL_SetError("Failed to allocate enough JVM local references");
   5.805 -    }
   5.806 -
   5.807 -    if (ctx) {
   5.808 -        if (release) {
   5.809 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
   5.810 -        }
   5.811 -
   5.812 -        if (ctx->hidden.androidio.assetFileDescriptorRef) {
   5.813 -            jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
   5.814 -            jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   5.815 -                    "close", "()V");
   5.816 -            mEnv->CallVoidMethod(inputStream, mid);
   5.817 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
   5.818 -            if (Android_JNI_ExceptionOccurred()) {
   5.819 -                result = -1;
   5.820 -            }
   5.821 -        }
   5.822 -        else {
   5.823 -            jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
   5.824 -
   5.825 -            // inputStream.close();
   5.826 -            jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
   5.827 -                    "close", "()V");
   5.828 -            mEnv->CallVoidMethod(inputStream, mid);
   5.829 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
   5.830 -            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
   5.831 -            if (Android_JNI_ExceptionOccurred()) {
   5.832 -                result = -1;
   5.833 -            }
   5.834 -        }
   5.835 -
   5.836 -        if (release) {
   5.837 -            SDL_FreeRW(ctx);
   5.838 -        }
   5.839 -    }
   5.840 -
   5.841 -    return result;
   5.842 -}
   5.843 -
   5.844 -
   5.845 -extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
   5.846 -{
   5.847 -    return ctx->hidden.androidio.size;
   5.848 -}
   5.849 -
   5.850 -extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
   5.851 -{
   5.852 -    if (ctx->hidden.androidio.assetFileDescriptorRef) {
   5.853 -        switch (whence) {
   5.854 -            case RW_SEEK_SET:
   5.855 -                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   5.856 -                offset += ctx->hidden.androidio.offset;
   5.857 -                break;
   5.858 -            case RW_SEEK_CUR:
   5.859 -                offset += ctx->hidden.androidio.position;
   5.860 -                if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
   5.861 -                offset += ctx->hidden.androidio.offset;
   5.862 -                break;
   5.863 -            case RW_SEEK_END:
   5.864 -                offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
   5.865 -                break;
   5.866 -            default:
   5.867 -                return SDL_SetError("Unknown value for 'whence'");
   5.868 -        }
   5.869 -        whence = SEEK_SET;
   5.870 -
   5.871 -        off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
   5.872 -        if (ret == -1) return -1;
   5.873 -        ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
   5.874 -    } else {
   5.875 -        Sint64 newPosition;
   5.876 -
   5.877 -        switch (whence) {
   5.878 -            case RW_SEEK_SET:
   5.879 -                newPosition = offset;
   5.880 -                break;
   5.881 -            case RW_SEEK_CUR:
   5.882 -                newPosition = ctx->hidden.androidio.position + offset;
   5.883 -                break;
   5.884 -            case RW_SEEK_END:
   5.885 -                newPosition = ctx->hidden.androidio.size + offset;
   5.886 -                break;
   5.887 -            default:
   5.888 -                return SDL_SetError("Unknown value for 'whence'");
   5.889 -        }
   5.890 -
   5.891 -        /* Validate the new position */
   5.892 -        if (newPosition < 0) {
   5.893 -            return SDL_Error(SDL_EFSEEK);
   5.894 -        }
   5.895 -        if (newPosition > ctx->hidden.androidio.size) {
   5.896 -            newPosition = ctx->hidden.androidio.size;
   5.897 -        }
   5.898 -
   5.899 -        Sint64 movement = newPosition - ctx->hidden.androidio.position;
   5.900 -        if (movement > 0) {
   5.901 -            unsigned char buffer[4096];
   5.902 -
   5.903 -            // The easy case where we're seeking forwards
   5.904 -            while (movement > 0) {
   5.905 -                Sint64 amount = sizeof (buffer);
   5.906 -                if (amount > movement) {
   5.907 -                    amount = movement;
   5.908 -                }
   5.909 -                size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
   5.910 -                if (result <= 0) {
   5.911 -                    // Failed to read/skip the required amount, so fail
   5.912 -                    return -1;
   5.913 -                }
   5.914 -
   5.915 -                movement -= result;
   5.916 -            }
   5.917 -
   5.918 -        } else if (movement < 0) {
   5.919 -            // We can't seek backwards so we have to reopen the file and seek
   5.920 -            // forwards which obviously isn't very efficient
   5.921 -            Android_JNI_FileClose(ctx, false);
   5.922 -            Android_JNI_FileOpen(ctx);
   5.923 -            Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
   5.924 -        }
   5.925 -    }
   5.926 -
   5.927 -    return ctx->hidden.androidio.position;
   5.928 -
   5.929 -}
   5.930 -
   5.931 -extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
   5.932 -{
   5.933 -    return Android_JNI_FileClose(ctx, true);
   5.934 -}
   5.935 -
   5.936 -// returns a new global reference which needs to be released later
   5.937 -static jobject Android_JNI_GetSystemServiceObject(const char* name)
   5.938 -{
   5.939 -    LocalReferenceHolder refs(__FUNCTION__);
   5.940 -    JNIEnv* env = Android_JNI_GetEnv();
   5.941 -    if (!refs.init(env)) {
   5.942 -        return NULL;
   5.943 -    }
   5.944 -
   5.945 -    jstring service = env->NewStringUTF(name);
   5.946 -
   5.947 -    jmethodID mid;
   5.948 -
   5.949 -    mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
   5.950 -    jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
   5.951 -
   5.952 -    mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
   5.953 -    jobject manager = env->CallObjectMethod(context, mid, service);
   5.954 -
   5.955 -    env->DeleteLocalRef(service);
   5.956 -
   5.957 -    return manager ? env->NewGlobalRef(manager) : NULL;
   5.958 -}
   5.959 -
   5.960 -#define SETUP_CLIPBOARD(error) \
   5.961 -    LocalReferenceHolder refs(__FUNCTION__); \
   5.962 -    JNIEnv* env = Android_JNI_GetEnv(); \
   5.963 -    if (!refs.init(env)) { \
   5.964 -        return error; \
   5.965 -    } \
   5.966 -    jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
   5.967 -    if (!clipboard) { \
   5.968 -        return error; \
   5.969 -    }
   5.970 -
   5.971 -extern "C" int Android_JNI_SetClipboardText(const char* text)
   5.972 -{
   5.973 -    SETUP_CLIPBOARD(-1)
   5.974 -
   5.975 -    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
   5.976 -    jstring string = env->NewStringUTF(text);
   5.977 -    env->CallVoidMethod(clipboard, mid, string);
   5.978 -    env->DeleteGlobalRef(clipboard);
   5.979 -    env->DeleteLocalRef(string);
   5.980 -    return 0;
   5.981 -}
   5.982 -
   5.983 -extern "C" char* Android_JNI_GetClipboardText()
   5.984 -{
   5.985 -    SETUP_CLIPBOARD(SDL_strdup(""))
   5.986 -
   5.987 -    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
   5.988 -    jobject sequence = env->CallObjectMethod(clipboard, mid);
   5.989 -    env->DeleteGlobalRef(clipboard);
   5.990 -    if (sequence) {
   5.991 -        mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
   5.992 -        jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
   5.993 -        const char* utf = env->GetStringUTFChars(string, 0);
   5.994 -        if (utf) {
   5.995 -            char* text = SDL_strdup(utf);
   5.996 -            env->ReleaseStringUTFChars(string, utf);
   5.997 -            return text;
   5.998 -        }
   5.999 -    }
  5.1000 -    return SDL_strdup("");
  5.1001 -}
  5.1002 -
  5.1003 -extern "C" SDL_bool Android_JNI_HasClipboardText()
  5.1004 -{
  5.1005 -    SETUP_CLIPBOARD(SDL_FALSE)
  5.1006 -
  5.1007 -    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
  5.1008 -    jboolean has = env->CallBooleanMethod(clipboard, mid);
  5.1009 -    env->DeleteGlobalRef(clipboard);
  5.1010 -    return has ? SDL_TRUE : SDL_FALSE;
  5.1011 -}
  5.1012 -
  5.1013 -
  5.1014 -// returns 0 on success or -1 on error (others undefined then)
  5.1015 -// returns truthy or falsy value in plugged, charged and battery
  5.1016 -// returns the value in seconds and percent or -1 if not available
  5.1017 -extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
  5.1018 -{
  5.1019 -    LocalReferenceHolder refs(__FUNCTION__);
  5.1020 -    JNIEnv* env = Android_JNI_GetEnv();
  5.1021 -    if (!refs.init(env)) {
  5.1022 -        return -1;
  5.1023 -    }
  5.1024 -
  5.1025 -    jmethodID mid;
  5.1026 -
  5.1027 -    mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
  5.1028 -    jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
  5.1029 -
  5.1030 -    jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
  5.1031 -
  5.1032 -    jclass cls = env->FindClass("android/content/IntentFilter");
  5.1033 -
  5.1034 -    mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  5.1035 -    jobject filter = env->NewObject(cls, mid, action);
  5.1036 -
  5.1037 -    env->DeleteLocalRef(action);
  5.1038 -
  5.1039 -    mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
  5.1040 -    jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
  5.1041 -
  5.1042 -    env->DeleteLocalRef(filter);
  5.1043 -
  5.1044 -    cls = env->GetObjectClass(intent);
  5.1045 -
  5.1046 -    jstring iname;
  5.1047 -    jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
  5.1048 -
  5.1049 -#define GET_INT_EXTRA(var, key) \
  5.1050 -    iname = env->NewStringUTF(key); \
  5.1051 -    int var = env->CallIntMethod(intent, imid, iname, -1); \
  5.1052 -    env->DeleteLocalRef(iname);
  5.1053 -
  5.1054 -    jstring bname;
  5.1055 -    jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
  5.1056 -
  5.1057 -#define GET_BOOL_EXTRA(var, key) \
  5.1058 -    bname = env->NewStringUTF(key); \
  5.1059 -    int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
  5.1060 -    env->DeleteLocalRef(bname);
  5.1061 -
  5.1062 -    if (plugged) {
  5.1063 -        GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
  5.1064 -        if (plug == -1) {
  5.1065 -            return -1;
  5.1066 -        }
  5.1067 -        // 1 == BatteryManager.BATTERY_PLUGGED_AC
  5.1068 -        // 2 == BatteryManager.BATTERY_PLUGGED_USB
  5.1069 -        *plugged = (0 < plug) ? 1 : 0;
  5.1070 -    }
  5.1071 -
  5.1072 -    if (charged) {
  5.1073 -        GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
  5.1074 -        if (status == -1) {
  5.1075 -            return -1;
  5.1076 -        }
  5.1077 -        // 5 == BatteryManager.BATTERY_STATUS_FULL
  5.1078 -        *charged = (status == 5) ? 1 : 0;
  5.1079 -    }
  5.1080 -
  5.1081 -    if (battery) {
  5.1082 -        GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
  5.1083 -        *battery = present ? 1 : 0;
  5.1084 -    }
  5.1085 -
  5.1086 -    if (seconds) {
  5.1087 -        *seconds = -1; // not possible
  5.1088 -    }
  5.1089 -
  5.1090 -    if (percent) {
  5.1091 -        GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
  5.1092 -        GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
  5.1093 -        if ((level == -1) || (scale == -1)) {
  5.1094 -            return -1;
  5.1095 -        }
  5.1096 -        *percent = level * 100 / scale;
  5.1097 -    }
  5.1098 -
  5.1099 -    env->DeleteLocalRef(intent);
  5.1100 -
  5.1101 -    return 0;
  5.1102 -}
  5.1103 -
  5.1104 -// sends message to be handled on the UI event dispatch thread
  5.1105 -extern "C" int Android_JNI_SendMessage(int command, int param)
  5.1106 -{
  5.1107 -    JNIEnv *env = Android_JNI_GetEnv();
  5.1108 -    if (!env) {
  5.1109 -        return -1;
  5.1110 -    }
  5.1111 -    jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)Z");
  5.1112 -    if (!mid) {
  5.1113 -        return -1;
  5.1114 -    }
  5.1115 -    jboolean success = env->CallStaticBooleanMethod(mActivityClass, mid, command, param);
  5.1116 -    return success ? 0 : -1;
  5.1117 -}
  5.1118 -
  5.1119 -extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
  5.1120 -{
  5.1121 -    JNIEnv *env = Android_JNI_GetEnv();
  5.1122 -    if (!env) {
  5.1123 -        return;
  5.1124 -    }
  5.1125 -
  5.1126 -    jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)Z");
  5.1127 -    if (!mid) {
  5.1128 -        return;
  5.1129 -    }
  5.1130 -    env->CallStaticBooleanMethod( mActivityClass, mid,
  5.1131 -                               inputRect->x,
  5.1132 -                               inputRect->y,
  5.1133 -                               inputRect->w,
  5.1134 -                               inputRect->h );
  5.1135 -}
  5.1136 -
  5.1137 -extern "C" void Android_JNI_HideTextInput()
  5.1138 -{
  5.1139 -    // has to match Activity constant
  5.1140 -    const int COMMAND_TEXTEDIT_HIDE = 3;
  5.1141 -    Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
  5.1142 -}
  5.1143 -
  5.1144 -//////////////////////////////////////////////////////////////////////////////
  5.1145 -//
  5.1146 -// Functions exposed to SDL applications in SDL_system.h
  5.1147 -//
  5.1148 -
  5.1149 -extern "C" void *SDL_AndroidGetJNIEnv()
  5.1150 -{
  5.1151 -    return Android_JNI_GetEnv();
  5.1152 -}
  5.1153 -
  5.1154 -
  5.1155 -
  5.1156 -extern "C" void *SDL_AndroidGetActivity()
  5.1157 -{
  5.1158 -    /* See SDL_system.h for caveats on using this function. */
  5.1159 -
  5.1160 -    jmethodID mid;
  5.1161 -
  5.1162 -    JNIEnv *env = Android_JNI_GetEnv();
  5.1163 -    if (!env) {
  5.1164 -        return NULL;
  5.1165 -    }
  5.1166 -
  5.1167 -    // return SDLActivity.getContext();
  5.1168 -    mid = env->GetStaticMethodID(mActivityClass,
  5.1169 -            "getContext","()Landroid/content/Context;");
  5.1170 -    return env->CallStaticObjectMethod(mActivityClass, mid);
  5.1171 -}
  5.1172 -
  5.1173 -extern "C" const char * SDL_AndroidGetInternalStoragePath()
  5.1174 -{
  5.1175 -    static char *s_AndroidInternalFilesPath = NULL;
  5.1176 -
  5.1177 -    if (!s_AndroidInternalFilesPath) {
  5.1178 -        LocalReferenceHolder refs(__FUNCTION__);
  5.1179 -        jmethodID mid;
  5.1180 -        jobject context;
  5.1181 -        jobject fileObject;
  5.1182 -        jstring pathString;
  5.1183 -        const char *path;
  5.1184 -
  5.1185 -        JNIEnv *env = Android_JNI_GetEnv();
  5.1186 -        if (!refs.init(env)) {
  5.1187 -            return NULL;
  5.1188 -        }
  5.1189 -
  5.1190 -        // context = SDLActivity.getContext();
  5.1191 -        mid = env->GetStaticMethodID(mActivityClass,
  5.1192 -                "getContext","()Landroid/content/Context;");
  5.1193 -        context = env->CallStaticObjectMethod(mActivityClass, mid);
  5.1194 -
  5.1195 -        // fileObj = context.getFilesDir();
  5.1196 -        mid = env->GetMethodID(env->GetObjectClass(context),
  5.1197 -                "getFilesDir", "()Ljava/io/File;");
  5.1198 -        fileObject = env->CallObjectMethod(context, mid);
  5.1199 -        if (!fileObject) {
  5.1200 -            SDL_SetError("Couldn't get internal directory");
  5.1201 -            return NULL;
  5.1202 -        }
  5.1203 -
  5.1204 -        // path = fileObject.getAbsolutePath();
  5.1205 -        mid = env->GetMethodID(env->GetObjectClass(fileObject),
  5.1206 -                "getAbsolutePath", "()Ljava/lang/String;");
  5.1207 -        pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  5.1208 -
  5.1209 -        path = env->GetStringUTFChars(pathString, NULL);
  5.1210 -        s_AndroidInternalFilesPath = SDL_strdup(path);
  5.1211 -        env->ReleaseStringUTFChars(pathString, path);
  5.1212 -    }
  5.1213 -    return s_AndroidInternalFilesPath;
  5.1214 -}
  5.1215 -
  5.1216 -extern "C" int SDL_AndroidGetExternalStorageState()
  5.1217 -{
  5.1218 -    LocalReferenceHolder refs(__FUNCTION__);
  5.1219 -    jmethodID mid;
  5.1220 -    jclass cls;
  5.1221 -    jstring stateString;
  5.1222 -    const char *state;
  5.1223 -    int stateFlags;
  5.1224 -
  5.1225 -    JNIEnv *env = Android_JNI_GetEnv();
  5.1226 -    if (!refs.init(env)) {
  5.1227 -        return 0;
  5.1228 -    }
  5.1229 -
  5.1230 -    cls = env->FindClass("android/os/Environment");
  5.1231 -    mid = env->GetStaticMethodID(cls,
  5.1232 -            "getExternalStorageState", "()Ljava/lang/String;");
  5.1233 -    stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
  5.1234 -
  5.1235 -    state = env->GetStringUTFChars(stateString, NULL);
  5.1236 -
  5.1237 -    // Print an info message so people debugging know the storage state
  5.1238 -    __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
  5.1239 -
  5.1240 -    if (SDL_strcmp(state, "mounted") == 0) {
  5.1241 -        stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
  5.1242 -                     SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
  5.1243 -    } else if (SDL_strcmp(state, "mounted_ro") == 0) {
  5.1244 -        stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
  5.1245 -    } else {
  5.1246 -        stateFlags = 0;
  5.1247 -    }
  5.1248 -    env->ReleaseStringUTFChars(stateString, state);
  5.1249 -
  5.1250 -    return stateFlags;
  5.1251 -}
  5.1252 -
  5.1253 -extern "C" const char * SDL_AndroidGetExternalStoragePath()
  5.1254 -{
  5.1255 -    static char *s_AndroidExternalFilesPath = NULL;
  5.1256 -
  5.1257 -    if (!s_AndroidExternalFilesPath) {
  5.1258 -        LocalReferenceHolder refs(__FUNCTION__);
  5.1259 -        jmethodID mid;
  5.1260 -        jobject context;
  5.1261 -        jobject fileObject;
  5.1262 -        jstring pathString;
  5.1263 -        const char *path;
  5.1264 -
  5.1265 -        JNIEnv *env = Android_JNI_GetEnv();
  5.1266 -        if (!refs.init(env)) {
  5.1267 -            return NULL;
  5.1268 -        }
  5.1269 -
  5.1270 -        // context = SDLActivity.getContext();
  5.1271 -        mid = env->GetStaticMethodID(mActivityClass,
  5.1272 -                "getContext","()Landroid/content/Context;");
  5.1273 -        context = env->CallStaticObjectMethod(mActivityClass, mid);
  5.1274 -
  5.1275 -        // fileObj = context.getExternalFilesDir();
  5.1276 -        mid = env->GetMethodID(env->GetObjectClass(context),
  5.1277 -                "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
  5.1278 -        fileObject = env->CallObjectMethod(context, mid, NULL);
  5.1279 -        if (!fileObject) {
  5.1280 -            SDL_SetError("Couldn't get external directory");
  5.1281 -            return NULL;
  5.1282 -        }
  5.1283 -
  5.1284 -        // path = fileObject.getAbsolutePath();
  5.1285 -        mid = env->GetMethodID(env->GetObjectClass(fileObject),
  5.1286 -                "getAbsolutePath", "()Ljava/lang/String;");
  5.1287 -        pathString = (jstring)env->CallObjectMethod(fileObject, mid);
  5.1288 -
  5.1289 -        path = env->GetStringUTFChars(pathString, NULL);
  5.1290 -        s_AndroidExternalFilesPath = SDL_strdup(path);
  5.1291 -        env->ReleaseStringUTFChars(pathString, path);
  5.1292 -    }
  5.1293 -    return s_AndroidExternalFilesPath;
  5.1294 -}
  5.1295 -
  5.1296 -#endif /* __ANDROID__ */
  5.1297 -
  5.1298 -/* vi: set ts=4 sw=4 expandtab: */
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/src/main/android/SDL_android_main.c	Mon Jul 22 02:51:45 2013 -0700
     6.3 @@ -0,0 +1,38 @@
     6.4 +
     6.5 +#include "SDL_config.h"
     6.6 +
     6.7 +#ifdef __ANDROID__
     6.8 +
     6.9 +/* Include the SDL main definition header */
    6.10 +#include "SDL_main.h"
    6.11 +
    6.12 +/*******************************************************************************
    6.13 +                 Functions called by JNI
    6.14 +*******************************************************************************/
    6.15 +#include <jni.h>
    6.16 +
    6.17 +// Called before SDL_main() to initialize JNI bindings in SDL library
    6.18 +extern void SDL_Android_Init(JNIEnv* env, jclass cls);
    6.19 +
    6.20 +// Start up the SDL app
    6.21 +void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
    6.22 +{
    6.23 +    /* This interface could expand with ABI negotiation, calbacks, etc. */
    6.24 +    SDL_Android_Init(env, cls);
    6.25 +
    6.26 +    SDL_SetMainReady();
    6.27 +
    6.28 +    /* Run the application code! */
    6.29 +    int status;
    6.30 +    char *argv[2];
    6.31 +    argv[0] = SDL_strdup("SDL_app");
    6.32 +    argv[1] = NULL;
    6.33 +    status = SDL_main(1, argv);
    6.34 +
    6.35 +    /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
    6.36 +    //exit(status);
    6.37 +}
    6.38 +
    6.39 +#endif /* __ANDROID__ */
    6.40 +
    6.41 +/* vi: set ts=4 sw=4 expandtab: */
     7.1 --- a/src/main/android/SDL_android_main.cpp	Mon Jul 22 22:54:00 2013 +0200
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,38 +0,0 @@
     7.4 -
     7.5 -#include "SDL_config.h"
     7.6 -
     7.7 -#ifdef __ANDROID__
     7.8 -
     7.9 -/* Include the SDL main definition header */
    7.10 -#include "SDL_main.h"
    7.11 -
    7.12 -/*******************************************************************************
    7.13 -                 Functions called by JNI
    7.14 -*******************************************************************************/
    7.15 -#include <jni.h>
    7.16 -
    7.17 -// Called before SDL_main() to initialize JNI bindings in SDL library
    7.18 -extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls);
    7.19 -
    7.20 -// Start up the SDL app
    7.21 -extern "C" void Java_org_libsdl_app_SDLActivity_nativeInit(JNIEnv* env, jclass cls, jobject obj)
    7.22 -{
    7.23 -    /* This interface could expand with ABI negotiation, calbacks, etc. */
    7.24 -    SDL_Android_Init(env, cls);
    7.25 -
    7.26 -    SDL_SetMainReady();
    7.27 -
    7.28 -    /* Run the application code! */
    7.29 -    int status;
    7.30 -    char *argv[2];
    7.31 -    argv[0] = SDL_strdup("SDL_app");
    7.32 -    argv[1] = NULL;
    7.33 -    status = SDL_main(1, argv);
    7.34 -
    7.35 -    /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
    7.36 -    //exit(status);
    7.37 -}
    7.38 -
    7.39 -#endif /* __ANDROID__ */
    7.40 -
    7.41 -/* vi: set ts=4 sw=4 expandtab: */