src/core/android/SDL_android.c
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Thu, 17 Sep 2015 22:30:24 +0200
changeset 9870 6dd5ab47534b
parent 9869 4ba43d626c4a
child 9873 b0f121cfa074
permissions -rw-r--r--
Android: Fixed trying to read from APK expansion files without version hint set.

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