src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Sun, 08 Jan 2012 01:05:25 -0500
changeset 6186 e565ac981de6
parent 6138 4c64952a58fb
child 6189 b5a665fbaedc
permissions -rwxr-xr-x
Fixed bug 1293 - [Android] Support Pause/Resume

Gabriel Jacobo 2011-12-23 12:55:11 PST

The attached files provide some improvement over the current handling of
pause/resume in Android.
- I disabled the exit(status) instruction in SDL_main as that makes the entire
app instead of the SDL thread exit (while not needed for pause/resume it is
needed for Live Wallpapers, an SDLActivity for which I'll upload in a separate
bug).
- Added nativePause and nativeResume which basically just mark the window as
visible/hidden, something that the end user needs to take into consideration
(ideally pausing the event loop).

Also, this arrangement creates a new GL context when needed, which at least in
my test system is every time you go away from the app and come back to it. So,
this means that the textures need to be generated again after resuming (a
problem the end user didn't have before because the app exited completely when
it should've been pausing). I'd like to know if there's a standard way of
letting the user know that the GL context has changed and that he needs to
refresh his textures, or if this is out of the scope of the library and each
user handles it in their own way (I don't know how/if this same thing is
handled in the iPhone backend, but it would be wise to try to imitate that).

Gabriel Jacobo 2011-12-23 12:57:10 PST
Also, in the SDLActivity the EGL handling code is moved up to the Activity from
the Surface code, as I think it is possible (in theory) that the surface is
destroyed temporarily while the context remains alive (though in practice in my
test system this is not the case)
slouken@4964
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6138
     3
  Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
slouken@4964
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@4964
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@4964
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@4964
    20
*/
slouken@4964
    21
#include "SDL_config.h"
slouken@5222
    22
#include "SDL_stdinc.h"
slouken@4964
    23
slouken@6044
    24
#ifdef __ANDROID__
slouken@6044
    25
slouken@4989
    26
#include "SDL_android.h"
slouken@4989
    27
slouken@4980
    28
extern "C" {
slouken@5092
    29
#include "../../events/SDL_events_c.h"
slouken@5092
    30
#include "../../video/android/SDL_androidkeyboard.h"
slouken@5092
    31
#include "../../video/android/SDL_androidtouch.h"
slouken@5092
    32
#include "../../video/android/SDL_androidvideo.h"
slouken@4995
    33
icculus@5996
    34
#include <android/log.h>
icculus@5996
    35
#define LOG_TAG "SDL_android"
icculus@5996
    36
//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
icculus@5996
    37
//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
icculus@5996
    38
#define LOGI(...) do {} while (false)
icculus@5996
    39
#define LOGE(...) do {} while (false)
icculus@5996
    40
icculus@5996
    41
slouken@4995
    42
/* Impelemented in audio/android/SDL_androidaudio.c */
slouken@4995
    43
extern void Android_RunAudioThread();
slouken@4995
    44
} // C
slouken@4980
    45
slouken@4964
    46
/*******************************************************************************
slouken@4964
    47
 This file links the Java side of Android with libsdl
slouken@4964
    48
*******************************************************************************/
slouken@4964
    49
#include <jni.h>
slouken@4964
    50
#include <android/log.h>
slouken@4964
    51
slouken@4964
    52
slouken@4964
    53
/*******************************************************************************
slouken@4964
    54
                               Globals
slouken@4964
    55
*******************************************************************************/
slouken@4995
    56
static JNIEnv* mEnv = NULL;
slouken@4995
    57
static JNIEnv* mAudioEnv = NULL;
icculus@5996
    58
static JavaVM* mJavaVM;
slouken@4964
    59
slouken@4995
    60
// Main activity
slouken@4998
    61
static jclass mActivityClass;
slouken@4964
    62
slouken@4995
    63
// method signatures
slouken@4995
    64
static jmethodID midCreateGLContext;
slouken@4995
    65
static jmethodID midFlipBuffers;
slouken@4995
    66
static jmethodID midAudioInit;
slouken@4995
    67
static jmethodID midAudioWriteShortBuffer;
slouken@4995
    68
static jmethodID midAudioWriteByteBuffer;
slouken@4996
    69
static jmethodID midAudioQuit;
slouken@4964
    70
slouken@4995
    71
// Accelerometer data storage
slouken@5000
    72
static float fLastAccelerometer[3];
slouken@4964
    73
slouken@4964
    74
slouken@4964
    75
/*******************************************************************************
slouken@4964
    76
                 Functions called by JNI
slouken@4964
    77
*******************************************************************************/
slouken@4964
    78
slouken@4964
    79
// Library init
slouken@4964
    80
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
slouken@4964
    81
{
icculus@5996
    82
    JNIEnv *env;
icculus@5996
    83
    mJavaVM = vm;
icculus@5996
    84
    LOGI("JNI_OnLoad called");
icculus@5996
    85
    if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
icculus@5996
    86
        LOGE("Failed to get the environment using GetEnv()");
icculus@5996
    87
        return -1;
icculus@5996
    88
    }
icculus@5996
    89
slouken@4964
    90
    return JNI_VERSION_1_4;
slouken@4964
    91
}
slouken@4964
    92
slouken@4964
    93
// Called before SDL_main() to initialize JNI bindings
slouken@4998
    94
extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
slouken@4964
    95
{
slouken@4964
    96
    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
slouken@4964
    97
slouken@4998
    98
    mEnv = env;
slouken@4998
    99
    mActivityClass = cls;
slouken@4998
   100
slouken@4998
   101
    midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
slouken@5222
   102
                                "createGLContext","(II)Z");
slouken@4998
   103
    midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   104
                                "flipBuffers","()V");
slouken@4998
   105
    midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
slouken@4998
   106
                                "audioInit", "(IZZI)Ljava/lang/Object;");
slouken@4998
   107
    midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   108
                                "audioWriteShortBuffer", "([S)V");
slouken@4998
   109
    midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   110
                                "audioWriteByteBuffer", "([B)V");
slouken@4998
   111
    midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   112
                                "audioQuit", "()V");
slouken@4964
   113
slouken@4995
   114
    if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
slouken@4996
   115
       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
slouken@4996
   116
        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
slouken@4964
   117
    }
icculus@5996
   118
    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
slouken@4964
   119
}
slouken@4964
   120
slouken@4995
   121
// Resize
slouken@4995
   122
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
slouken@4998
   123
                                    JNIEnv* env, jclass jcls,
slouken@4995
   124
                                    jint width, jint height, jint format)
slouken@4995
   125
{
slouken@4995
   126
    Android_SetScreenResolution(width, height, format);
slouken@4995
   127
}
slouken@4995
   128
slouken@4964
   129
// Keydown
slouken@4995
   130
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
slouken@4998
   131
                                    JNIEnv* env, jclass jcls, jint keycode)
slouken@4964
   132
{
slouken@4980
   133
    Android_OnKeyDown(keycode);
slouken@4964
   134
}
slouken@4964
   135
slouken@4964
   136
// Keyup
slouken@4995
   137
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
slouken@4998
   138
                                    JNIEnv* env, jclass jcls, jint keycode)
slouken@4964
   139
{
slouken@4980
   140
    Android_OnKeyUp(keycode);
slouken@4964
   141
}
slouken@4964
   142
slouken@4964
   143
// Touch
slouken@4995
   144
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
slouken@4998
   145
                                    JNIEnv* env, jclass jcls,
icculus@5982
   146
                                    jint touch_device_id_in, jint pointer_finger_id_in,
slouken@4995
   147
                                    jint action, jfloat x, jfloat y, jfloat p)
slouken@4964
   148
{
icculus@5982
   149
    Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
slouken@4964
   150
}
slouken@4964
   151
slouken@4995
   152
// Accelerometer
slouken@4964
   153
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
slouken@4998
   154
                                    JNIEnv* env, jclass jcls,
slouken@4995
   155
                                    jfloat x, jfloat y, jfloat z)
slouken@4964
   156
{
slouken@4964
   157
    fLastAccelerometer[0] = x;
slouken@4964
   158
    fLastAccelerometer[1] = y;
slouken@4964
   159
    fLastAccelerometer[2] = z;   
slouken@4964
   160
}
slouken@4964
   161
slouken@4995
   162
// Quit
slouken@4995
   163
extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
slouken@4998
   164
                                    JNIEnv* env, jclass cls)
slouken@4995
   165
{    
slouken@4995
   166
    // Inject a SDL_QUIT event
slouken@4995
   167
    SDL_SendQuit();
slouken@4995
   168
}
slouken@4995
   169
slouken@6186
   170
// Pause
slouken@6186
   171
extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
slouken@6186
   172
                                    JNIEnv* env, jclass cls)
slouken@6186
   173
{
slouken@6186
   174
    if (Android_Window) {
slouken@6186
   175
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_HIDDEN, 0, 0);
slouken@6186
   176
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
slouken@6186
   177
    }
slouken@6186
   178
}
slouken@6186
   179
slouken@6186
   180
// Resume
slouken@6186
   181
extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
slouken@6186
   182
                                    JNIEnv* env, jclass cls)
slouken@6186
   183
{
slouken@6186
   184
    if (Android_Window) {
slouken@6186
   185
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_SHOWN, 0, 0);
slouken@6186
   186
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
slouken@6186
   187
    }
slouken@6186
   188
}
slouken@6186
   189
slouken@4995
   190
extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
slouken@4998
   191
                                    JNIEnv* env, jclass cls)
slouken@4995
   192
{
slouken@4998
   193
    /* This is the audio thread, with a different environment */
slouken@4998
   194
    mAudioEnv = env;
slouken@4998
   195
slouken@4996
   196
    Android_RunAudioThread();
slouken@4995
   197
}
slouken@4964
   198
slouken@4964
   199
slouken@4964
   200
/*******************************************************************************
slouken@4964
   201
             Functions called by SDL into Java
slouken@4964
   202
*******************************************************************************/
slouken@5222
   203
extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
slouken@4964
   204
{
slouken@5222
   205
    if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
slouken@5222
   206
        return SDL_TRUE;
slouken@5222
   207
    } else {
slouken@5222
   208
        return SDL_FALSE;
slouken@5222
   209
    }
slouken@4964
   210
}
slouken@4964
   211
slouken@4989
   212
extern "C" void Android_JNI_SwapWindow()
slouken@4964
   213
{
slouken@4998
   214
    mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
slouken@4998
   215
}
slouken@4998
   216
slouken@4998
   217
extern "C" void Android_JNI_SetActivityTitle(const char *title)
slouken@4998
   218
{
slouken@4998
   219
    jmethodID mid;
slouken@4998
   220
slouken@4998
   221
    mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
slouken@4998
   222
    if (mid) {
slouken@4998
   223
        mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
slouken@4998
   224
    }
slouken@4964
   225
}
slouken@4964
   226
slouken@5000
   227
extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
slouken@5000
   228
{
slouken@5000
   229
    int i;
slouken@5000
   230
    for (i = 0; i < 3; ++i) {
slouken@5000
   231
        values[i] = fLastAccelerometer[i];
slouken@5000
   232
    }
slouken@5000
   233
}
slouken@5000
   234
slouken@4995
   235
//
slouken@4995
   236
// Audio support
slouken@4995
   237
//
slouken@4996
   238
static jboolean audioBuffer16Bit = JNI_FALSE;
slouken@4996
   239
static jboolean audioBufferStereo = JNI_FALSE;
slouken@4996
   240
static jobject audioBuffer = NULL;
slouken@4996
   241
static void* audioBufferPinned = NULL;
slouken@4995
   242
slouken@4995
   243
extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
slouken@4964
   244
{
slouken@4996
   245
    int audioBufferFrames;
slouken@4964
   246
icculus@5996
   247
    int status;
icculus@5996
   248
    JNIEnv *env;
icculus@5996
   249
    static bool isAttached = false;    
icculus@5996
   250
    status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
icculus@5996
   251
    if(status < 0) {
icculus@5996
   252
        LOGE("callback_handler: failed to get JNI environment, assuming native thread");
icculus@5996
   253
        status = mJavaVM->AttachCurrentThread(&env, NULL);
icculus@5996
   254
        if(status < 0) {
icculus@5996
   255
            LOGE("callback_handler: failed to attach current thread");
icculus@5996
   256
            return 0;
icculus@5996
   257
        }
icculus@5996
   258
        isAttached = true;
icculus@5996
   259
    }
icculus@5996
   260
icculus@5996
   261
    
slouken@4996
   262
    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
slouken@4996
   263
    audioBuffer16Bit = is16Bit;
slouken@4996
   264
    audioBufferStereo = channelCount > 1;
slouken@4964
   265
icculus@5996
   266
    audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
slouken@4995
   267
slouken@4996
   268
    if (audioBuffer == NULL) {
slouken@4996
   269
        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
slouken@4996
   270
        return 0;
slouken@4996
   271
    }
icculus@5996
   272
    audioBuffer = env->NewGlobalRef(audioBuffer);
slouken@4995
   273
slouken@4996
   274
    jboolean isCopy = JNI_FALSE;
slouken@4996
   275
    if (audioBuffer16Bit) {
icculus@5996
   276
        audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
icculus@5996
   277
        audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
slouken@4996
   278
    } else {
icculus@5996
   279
        audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
icculus@5996
   280
        audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
slouken@4996
   281
    }
slouken@4996
   282
    if (audioBufferStereo) {
slouken@4996
   283
        audioBufferFrames /= 2;
slouken@4996
   284
    }
icculus@5996
   285
 
icculus@5996
   286
    if (isAttached) {
icculus@5996
   287
        mJavaVM->DetachCurrentThread();
icculus@5996
   288
    }
slouken@4996
   289
slouken@4996
   290
    return audioBufferFrames;
slouken@4995
   291
}
slouken@4995
   292
slouken@4996
   293
extern "C" void * Android_JNI_GetAudioBuffer()
slouken@4995
   294
{
slouken@4996
   295
    return audioBufferPinned;
slouken@4995
   296
}
slouken@4995
   297
slouken@4996
   298
extern "C" void Android_JNI_WriteAudioBuffer()
slouken@4995
   299
{
slouken@4996
   300
    if (audioBuffer16Bit) {
slouken@4996
   301
        mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
slouken@4998
   302
        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
slouken@4996
   303
    } else {
slouken@4996
   304
        mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
slouken@4998
   305
        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
slouken@4996
   306
    }
slouken@4995
   307
slouken@4996
   308
    /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
slouken@4995
   309
}
slouken@4995
   310
slouken@4995
   311
extern "C" void Android_JNI_CloseAudioDevice()
slouken@4995
   312
{
icculus@5996
   313
    int status;
icculus@5996
   314
    JNIEnv *env;
icculus@5996
   315
    static bool isAttached = false;    
icculus@5996
   316
    status = mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
icculus@5996
   317
    if(status < 0) {
icculus@5996
   318
        LOGE("callback_handler: failed to get JNI environment, assuming native thread");
icculus@5996
   319
        status = mJavaVM->AttachCurrentThread(&env, NULL);
icculus@5996
   320
        if(status < 0) {
icculus@5996
   321
            LOGE("callback_handler: failed to attach current thread");
icculus@5996
   322
            return;
icculus@5996
   323
        }
icculus@5996
   324
        isAttached = true;
icculus@5996
   325
    }
icculus@5996
   326
icculus@5996
   327
    env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
slouken@4964
   328
slouken@4997
   329
    if (audioBuffer) {
icculus@5996
   330
        env->DeleteGlobalRef(audioBuffer);
slouken@4997
   331
        audioBuffer = NULL;
slouken@4997
   332
        audioBufferPinned = NULL;
slouken@4997
   333
    }
icculus@5996
   334
icculus@5996
   335
    if (isAttached) {
icculus@5996
   336
        mJavaVM->DetachCurrentThread();
icculus@5996
   337
    }
slouken@4964
   338
}
slouken@4981
   339
tim@5650
   340
// Test for an exception and call SDL_SetError with its detail if one occurs
tim@5650
   341
static bool Android_JNI_ExceptionOccurred()
tim@5650
   342
{
tim@5650
   343
    jthrowable exception = mEnv->ExceptionOccurred();
tim@5650
   344
    if (exception != NULL) {
tim@5650
   345
        jmethodID mid;
tim@5650
   346
tim@5650
   347
        // Until this happens most JNI operations have undefined behaviour
tim@5650
   348
        mEnv->ExceptionClear();
tim@5650
   349
tim@5650
   350
        jclass exceptionClass = mEnv->GetObjectClass(exception);
tim@5650
   351
        jclass classClass = mEnv->FindClass("java/lang/Class");
tim@5650
   352
tim@5650
   353
        mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
tim@5650
   354
        jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
tim@5650
   355
        const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
tim@5650
   356
tim@5650
   357
        mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
icculus@5860
   358
        jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
tim@5650
   359
tim@5650
   360
        if (exceptionMessage != NULL) {
tim@5650
   361
            const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
tim@5650
   362
                    exceptionMessage, 0);
tim@5650
   363
            SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
tim@5650
   364
            mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
tim@5650
   365
            mEnv->DeleteLocalRef(exceptionMessage);
tim@5650
   366
        } else {
tim@5650
   367
            SDL_SetError("%s", exceptionNameUTF8);
tim@5650
   368
        }
tim@5650
   369
tim@5650
   370
        mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
tim@5650
   371
        mEnv->DeleteLocalRef(exceptionName);
tim@5650
   372
        mEnv->DeleteLocalRef(classClass);
tim@5650
   373
        mEnv->DeleteLocalRef(exceptionClass);
tim@5650
   374
        mEnv->DeleteLocalRef(exception);
tim@5650
   375
tim@5650
   376
        return true;
tim@5650
   377
    }
tim@5650
   378
tim@5650
   379
    return false;
tim@5650
   380
}
tim@5650
   381
icculus@5582
   382
static int Android_JNI_FileOpen(SDL_RWops* ctx)
icculus@5582
   383
{
tim@5650
   384
    int result = 0;
tim@5650
   385
tim@5650
   386
    jmethodID mid;
tim@5650
   387
    jobject context;
tim@5650
   388
    jobject assetManager;
tim@5650
   389
    jobject inputStream;
tim@5650
   390
    jclass channels;
tim@5650
   391
    jobject readableByteChannel;
tim@5650
   392
    jstring fileNameJString;
tim@5650
   393
tim@5650
   394
    bool allocatedLocalFrame = false;
tim@5650
   395
tim@5650
   396
    if (mEnv->PushLocalFrame(16) < 0) {
tim@5650
   397
        SDL_SetError("Failed to allocate enough JVM local references");
tim@5650
   398
        goto failure;
tim@5650
   399
    } else {
tim@5650
   400
        allocatedLocalFrame = true;
tim@5650
   401
    }
tim@5650
   402
tim@5650
   403
    fileNameJString = (jstring)ctx->hidden.androidio.fileName;
icculus@5582
   404
icculus@5582
   405
    // context = SDLActivity.getContext();
tim@5650
   406
    mid = mEnv->GetStaticMethodID(mActivityClass,
icculus@5582
   407
            "getContext","()Landroid/content/Context;");
tim@5650
   408
    context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
icculus@5582
   409
icculus@5582
   410
    // assetManager = context.getAssets();
icculus@5582
   411
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
tim@5650
   412
            "getAssets", "()Landroid/content/res/AssetManager;");
tim@5650
   413
    assetManager = mEnv->CallObjectMethod(context, mid);
icculus@5582
   414
icculus@5582
   415
    // inputStream = assetManager.open(<filename>);
icculus@5582
   416
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
icculus@5582
   417
            "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
tim@5650
   418
    inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
tim@5650
   419
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   420
        goto failure;
icculus@5582
   421
    }
icculus@5582
   422
tim@5650
   423
    ctx->hidden.androidio.inputStream = inputStream;
tim@5650
   424
    ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
tim@5650
   425
icculus@5582
   426
    // Despite all the visible documentation on [Asset]InputStream claiming
icculus@5582
   427
    // that the .available() method is not guaranteed to return the entire file
icculus@5582
   428
    // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
icculus@5582
   429
    // android/apis/content/ReadAsset.java imply that Android's
icculus@5582
   430
    // AssetInputStream.available() /will/ always return the total file size
icculus@5582
   431
icculus@5582
   432
    // size = inputStream.available();
icculus@5582
   433
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
icculus@5582
   434
            "available", "()I");
icculus@5582
   435
    ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
tim@5650
   436
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   437
        goto failure;
icculus@5582
   438
    }
icculus@5582
   439
icculus@5582
   440
    // readableByteChannel = Channels.newChannel(inputStream);
tim@5650
   441
    channels = mEnv->FindClass("java/nio/channels/Channels");
icculus@5582
   442
    mid = mEnv->GetStaticMethodID(channels,
icculus@5582
   443
            "newChannel",
icculus@5582
   444
            "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
tim@5650
   445
    readableByteChannel = mEnv->CallStaticObjectMethod(
icculus@5582
   446
            channels, mid, inputStream);
tim@5650
   447
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   448
        goto failure;
icculus@5582
   449
    }
icculus@5582
   450
tim@5650
   451
    ctx->hidden.androidio.readableByteChannel = readableByteChannel;
tim@5650
   452
    ctx->hidden.androidio.readableByteChannelRef =
tim@5650
   453
        mEnv->NewGlobalRef(readableByteChannel);
tim@5650
   454
icculus@5582
   455
    // Store .read id for reading purposes
icculus@5582
   456
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
icculus@5582
   457
            "read", "(Ljava/nio/ByteBuffer;)I");
icculus@5582
   458
    ctx->hidden.androidio.readMethod = mid;
icculus@5582
   459
icculus@5582
   460
    ctx->hidden.androidio.position = 0;
icculus@5582
   461
tim@5650
   462
    if (false) {
tim@5650
   463
failure:
tim@5650
   464
        result = -1;
tim@5650
   465
tim@5650
   466
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
tim@5650
   467
tim@5650
   468
        if(ctx->hidden.androidio.inputStreamRef != NULL) {
tim@5650
   469
            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
tim@5650
   470
        }
tim@5650
   471
    }
tim@5650
   472
tim@5650
   473
    if (allocatedLocalFrame) {
tim@5650
   474
        mEnv->PopLocalFrame(NULL);
tim@5650
   475
    }
tim@5650
   476
tim@5650
   477
    return result;
icculus@5582
   478
}
icculus@5582
   479
icculus@5582
   480
extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
icculus@5582
   481
        const char* fileName, const char*)
icculus@5582
   482
{
icculus@5582
   483
    if (!ctx) {
icculus@5582
   484
        return -1;
icculus@5582
   485
    }
icculus@5582
   486
icculus@5582
   487
    jstring fileNameJString = mEnv->NewStringUTF(fileName);
icculus@5582
   488
    ctx->hidden.androidio.fileName = fileNameJString;
icculus@5582
   489
    ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
tim@5650
   490
    ctx->hidden.androidio.inputStreamRef = NULL;
tim@5650
   491
    mEnv->DeleteLocalRef(fileNameJString);
icculus@5582
   492
icculus@5582
   493
    return Android_JNI_FileOpen(ctx);
icculus@5582
   494
}
icculus@5582
   495
icculus@5582
   496
extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
icculus@5582
   497
        size_t size, size_t maxnum)
icculus@5582
   498
{
icculus@5582
   499
    int bytesRemaining = size * maxnum;
icculus@5582
   500
    int bytesRead = 0;
icculus@5582
   501
icculus@5582
   502
    jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
icculus@5582
   503
    jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
icculus@5582
   504
    jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
icculus@5582
   505
icculus@5582
   506
    while (bytesRemaining > 0) {
icculus@5582
   507
        // result = readableByteChannel.read(...);
icculus@5582
   508
        int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
icculus@5582
   509
tim@5650
   510
        if (Android_JNI_ExceptionOccurred()) {
tim@5650
   511
            mEnv->DeleteLocalRef(byteBuffer);
icculus@5582
   512
            return 0;
icculus@5582
   513
        }
icculus@5582
   514
icculus@5582
   515
        if (result < 0) {
icculus@5582
   516
            break;
icculus@5582
   517
        }
icculus@5582
   518
icculus@5582
   519
        bytesRemaining -= result;
icculus@5582
   520
        bytesRead += result;
icculus@5582
   521
        ctx->hidden.androidio.position += result;
icculus@5582
   522
    }
icculus@5582
   523
tim@5650
   524
    mEnv->DeleteLocalRef(byteBuffer);
tim@5650
   525
icculus@5582
   526
    return bytesRead / size;
icculus@5582
   527
}
icculus@5582
   528
icculus@5582
   529
extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
icculus@5582
   530
        size_t size, size_t num)
icculus@5582
   531
{
icculus@5582
   532
    SDL_SetError("Cannot write to Android package filesystem");
icculus@5582
   533
    return 0;
icculus@5582
   534
}
icculus@5582
   535
icculus@5582
   536
static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
icculus@5582
   537
{
icculus@5582
   538
    int result = 0;
icculus@5582
   539
icculus@5582
   540
    if (ctx) {
icculus@5582
   541
        if (release) {
icculus@5582
   542
            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
icculus@5582
   543
        }
icculus@5582
   544
icculus@5582
   545
        jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
icculus@5582
   546
icculus@5582
   547
        // inputStream.close();
icculus@5582
   548
        jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
icculus@5582
   549
                "close", "()V");
icculus@5582
   550
        mEnv->CallVoidMethod(inputStream, mid);
icculus@5582
   551
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
icculus@5582
   552
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
tim@5650
   553
        if (Android_JNI_ExceptionOccurred()) {
icculus@5582
   554
            result = -1;
icculus@5582
   555
        }
icculus@5582
   556
icculus@5582
   557
        if (release) {
icculus@5582
   558
            SDL_FreeRW(ctx);
icculus@5582
   559
        }
icculus@5582
   560
    }
icculus@5582
   561
icculus@5582
   562
    return result;
icculus@5582
   563
}
icculus@5582
   564
icculus@5582
   565
icculus@5582
   566
extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
icculus@5582
   567
{
icculus@5582
   568
    long newPosition;
icculus@5582
   569
icculus@5582
   570
    switch (whence) {
icculus@5582
   571
        case RW_SEEK_SET:
icculus@5582
   572
            newPosition = offset;
icculus@5582
   573
            break;
icculus@5582
   574
        case RW_SEEK_CUR:
icculus@5582
   575
            newPosition = ctx->hidden.androidio.position + offset;
icculus@5582
   576
            break;
icculus@5582
   577
        case RW_SEEK_END:
icculus@5582
   578
            newPosition = ctx->hidden.androidio.size + offset;
icculus@5582
   579
            break;
icculus@5582
   580
        default:
icculus@5582
   581
            SDL_SetError("Unknown value for 'whence'");
icculus@5582
   582
            return -1;
icculus@5582
   583
    }
icculus@5582
   584
    if (newPosition < 0) {
icculus@5582
   585
        newPosition = 0;
icculus@5582
   586
    }
icculus@5582
   587
    if (newPosition > ctx->hidden.androidio.size) {
icculus@5582
   588
        newPosition = ctx->hidden.androidio.size;
icculus@5582
   589
    }
icculus@5582
   590
icculus@5582
   591
    long movement = newPosition - ctx->hidden.androidio.position;
icculus@5582
   592
    jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
icculus@5582
   593
icculus@5582
   594
    if (movement > 0) {
tim@5993
   595
        unsigned char buffer[1024];
tim@5993
   596
icculus@5582
   597
        // The easy case where we're seeking forwards
icculus@5582
   598
        while (movement > 0) {
icculus@5994
   599
            long amount = (long) sizeof (buffer);
icculus@5994
   600
            if (amount > movement) {
icculus@5994
   601
                amount = movement;
icculus@5994
   602
            }
icculus@5994
   603
            size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
tim@5993
   604
tim@5993
   605
            if (result <= 0) {
tim@5993
   606
                // Failed to read/skip the required amount, so fail
icculus@5582
   607
                return -1;
icculus@5582
   608
            }
tim@5993
   609
tim@5993
   610
            movement -= result;
icculus@5582
   611
        }
icculus@5582
   612
    } else if (movement < 0) {
icculus@5582
   613
        // We can't seek backwards so we have to reopen the file and seek
icculus@5582
   614
        // forwards which obviously isn't very efficient
icculus@5582
   615
        Android_JNI_FileClose(ctx, false);
icculus@5582
   616
        Android_JNI_FileOpen(ctx);
icculus@5582
   617
        Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
icculus@5582
   618
    }
icculus@5582
   619
icculus@5582
   620
    ctx->hidden.androidio.position = newPosition;
icculus@5582
   621
icculus@5582
   622
    return ctx->hidden.androidio.position;
icculus@5582
   623
}
icculus@5582
   624
icculus@5582
   625
extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
icculus@5582
   626
{
icculus@5582
   627
    return Android_JNI_FileClose(ctx, true);
icculus@5582
   628
}
icculus@5582
   629
slouken@6044
   630
#endif /* __ANDROID__ */
slouken@6044
   631
slouken@4981
   632
/* vi: set ts=4 sw=4 expandtab: */