src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 13 Sep 2014 02:15:18 -0700
changeset 9135 6cd8e6b54f4d
parent 8961 7dbbee37826b
child 9173 c677ab1148c5
permissions -rw-r--r--
Fixed bug 2415 - Message Boxes aren't implemented on Android

Philipp Wiesemann

I attached a patch for an incomplete implementation of the messagebox parts.

It was not tested on lots of devices yet and features a very fragile workaround to block the calling SDL thread while the dialog is handled on Android's UI thread. Although it works for testmessage.c I assume there are lot of situations were it may fail (standby, device rotation and other changes). Also not all flags and colors are implemented.

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