src/core/android/SDL_android.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 28 Aug 2017 10:03:39 -0700
changeset 11393 3e57ce45a890
parent 11355 6185fb86f046
child 11395 a8c29f5b679f
permissions -rw-r--r--
Fixed bug 2361 - [Android] Joysticks do not have unique IDs

David Brady

When I attempted to make a mapping file for Android gamepads, I quickly discovered that most of the ones that I have here show up as the same device (Broadcom Bluetooth HID), meaning that it was impossible to make mappings on Android, since every device looked the same.

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