src/core/android/SDL_android.cpp
author Sam Lantinga <slouken@libsdl.org>
Wed, 03 Oct 2012 20:49:16 -0700
changeset 6555 f2c03c06d987
parent 6464 ab55284b389f
child 6630 55910871076b
permissions -rw-r--r--
Fixed bug 1614 - SDL for Android does not implement TextInput API

Andrey Isakov 2012-10-03 08:30:25 PDT

I've found out in the process of porting one OS project to Android/SDL2 that
there is no support for TextInput events/APIs on Android.
So I implemented some kind of initial support of that feature, and at the very
least it seems to work fine with latin chars input with soft and hardware
keyboards on my Moto Milestone2. I've also tried playing around with more
complex IMEs, like japanese, logging the process and it seemed to work too. I'm
not sure since the app itself I am working on does not have support for
non-latin input.

The main point of the patch is to place a fake input view in the region
specified by SDL_SetTextInputRect and create a custom InputConnection for it.
The reason to make it a separate view is to support Android's pan&scan on input
feature properly. For details please refer to
http://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.html
Even though the manual states that SetTextInputRect is used to determine the
IME variants position, I thought this would be a proper use for this too.
slouken@4964
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@6138
     3
  Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
slouken@4964
     4
slouken@5535
     5
  This software is provided 'as-is', without any express or implied
slouken@5535
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@5535
     7
  arising from the use of this software.
slouken@4964
     8
slouken@5535
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@5535
    10
  including commercial applications, and to alter it and redistribute it
slouken@5535
    11
  freely, subject to the following restrictions:
slouken@4964
    12
slouken@5535
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@5535
    14
     claim that you wrote the original software. If you use this software
slouken@5535
    15
     in a product, an acknowledgment in the product documentation would be
slouken@5535
    16
     appreciated but is not required.
slouken@5535
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@5535
    18
     misrepresented as being the original software.
slouken@5535
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@4964
    20
*/
slouken@4964
    21
#include "SDL_config.h"
slouken@5222
    22
#include "SDL_stdinc.h"
slouken@6284
    23
#include "SDL_assert.h"
slouken@4964
    24
slouken@6044
    25
#ifdef __ANDROID__
slouken@6044
    26
slouken@4989
    27
#include "SDL_android.h"
slouken@4989
    28
slouken@4980
    29
extern "C" {
slouken@5092
    30
#include "../../events/SDL_events_c.h"
slouken@5092
    31
#include "../../video/android/SDL_androidkeyboard.h"
slouken@5092
    32
#include "../../video/android/SDL_androidtouch.h"
slouken@5092
    33
#include "../../video/android/SDL_androidvideo.h"
slouken@4995
    34
icculus@5996
    35
#include <android/log.h>
gabomdq@6354
    36
#include <pthread.h>
icculus@5996
    37
#define LOG_TAG "SDL_android"
icculus@5996
    38
//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
icculus@5996
    39
//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
icculus@5996
    40
#define LOGI(...) do {} while (false)
icculus@5996
    41
#define LOGE(...) do {} while (false)
icculus@5996
    42
icculus@5996
    43
slouken@6191
    44
/* Implemented in audio/android/SDL_androidaudio.c */
slouken@4995
    45
extern void Android_RunAudioThread();
slouken@4995
    46
} // C
slouken@4980
    47
slouken@4964
    48
/*******************************************************************************
slouken@4964
    49
 This file links the Java side of Android with libsdl
slouken@4964
    50
*******************************************************************************/
slouken@4964
    51
#include <jni.h>
slouken@4964
    52
#include <android/log.h>
slouken@4964
    53
slouken@4964
    54
slouken@4964
    55
/*******************************************************************************
slouken@4964
    56
                               Globals
slouken@4964
    57
*******************************************************************************/
gabomdq@6354
    58
static pthread_key_t mThreadKey;
icculus@5996
    59
static JavaVM* mJavaVM;
slouken@4964
    60
slouken@4995
    61
// Main activity
slouken@4998
    62
static jclass mActivityClass;
slouken@4964
    63
slouken@4995
    64
// method signatures
slouken@4995
    65
static jmethodID midCreateGLContext;
slouken@4995
    66
static jmethodID midFlipBuffers;
slouken@4995
    67
static jmethodID midAudioInit;
slouken@4995
    68
static jmethodID midAudioWriteShortBuffer;
slouken@4995
    69
static jmethodID midAudioWriteByteBuffer;
slouken@4996
    70
static jmethodID midAudioQuit;
slouken@4964
    71
slouken@4995
    72
// Accelerometer data storage
slouken@5000
    73
static float fLastAccelerometer[3];
slouken@6212
    74
static bool bHasNewData;
slouken@4964
    75
slouken@4964
    76
/*******************************************************************************
slouken@4964
    77
                 Functions called by JNI
slouken@4964
    78
*******************************************************************************/
slouken@4964
    79
slouken@4964
    80
// Library init
slouken@4964
    81
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
slouken@4964
    82
{
icculus@5996
    83
    JNIEnv *env;
icculus@5996
    84
    mJavaVM = vm;
icculus@5996
    85
    LOGI("JNI_OnLoad called");
icculus@5996
    86
    if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
icculus@5996
    87
        LOGE("Failed to get the environment using GetEnv()");
icculus@5996
    88
        return -1;
icculus@5996
    89
    }
gabomdq@6354
    90
    /*
gabomdq@6354
    91
     * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
gabomdq@6354
    92
     * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
gabomdq@6354
    93
     */
gabomdq@6354
    94
    if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
gabomdq@6354
    95
        __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
gabomdq@6354
    96
    }
gabomdq@6354
    97
    else {
gabomdq@6354
    98
        Android_JNI_SetupThread();
gabomdq@6354
    99
    }
icculus@5996
   100
slouken@4964
   101
    return JNI_VERSION_1_4;
slouken@4964
   102
}
slouken@4964
   103
slouken@4964
   104
// Called before SDL_main() to initialize JNI bindings
gabomdq@6354
   105
extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
slouken@4964
   106
{
slouken@4964
   107
    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
slouken@4964
   108
gabomdq@6354
   109
    Android_JNI_SetupThread();
gabomdq@6354
   110
gabomdq@6354
   111
    mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
slouken@4998
   112
slouken@4998
   113
    midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
slouken@5222
   114
                                "createGLContext","(II)Z");
slouken@4998
   115
    midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   116
                                "flipBuffers","()V");
slouken@4998
   117
    midAudioInit = mEnv->GetStaticMethodID(mActivityClass, 
slouken@4998
   118
                                "audioInit", "(IZZI)Ljava/lang/Object;");
slouken@4998
   119
    midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   120
                                "audioWriteShortBuffer", "([S)V");
slouken@4998
   121
    midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   122
                                "audioWriteByteBuffer", "([B)V");
slouken@4998
   123
    midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
slouken@4998
   124
                                "audioQuit", "()V");
slouken@4964
   125
slouken@6212
   126
    bHasNewData = false;
slouken@6212
   127
slouken@4995
   128
    if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
slouken@4996
   129
       !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
slouken@4996
   130
        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
slouken@4964
   131
    }
icculus@5996
   132
    __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
slouken@4964
   133
}
slouken@4964
   134
slouken@4995
   135
// Resize
slouken@4995
   136
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
slouken@4998
   137
                                    JNIEnv* env, jclass jcls,
slouken@4995
   138
                                    jint width, jint height, jint format)
slouken@4995
   139
{
slouken@4995
   140
    Android_SetScreenResolution(width, height, format);
slouken@4995
   141
}
slouken@4995
   142
slouken@4964
   143
// Keydown
slouken@4995
   144
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
slouken@4998
   145
                                    JNIEnv* env, jclass jcls, jint keycode)
slouken@4964
   146
{
slouken@4980
   147
    Android_OnKeyDown(keycode);
slouken@4964
   148
}
slouken@4964
   149
slouken@4964
   150
// Keyup
slouken@4995
   151
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
slouken@4998
   152
                                    JNIEnv* env, jclass jcls, jint keycode)
slouken@4964
   153
{
slouken@4980
   154
    Android_OnKeyUp(keycode);
slouken@4964
   155
}
slouken@4964
   156
slouken@4964
   157
// Touch
slouken@4995
   158
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
slouken@4998
   159
                                    JNIEnv* env, jclass jcls,
icculus@5982
   160
                                    jint touch_device_id_in, jint pointer_finger_id_in,
slouken@4995
   161
                                    jint action, jfloat x, jfloat y, jfloat p)
slouken@4964
   162
{
icculus@5982
   163
    Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
slouken@4964
   164
}
slouken@4964
   165
slouken@4995
   166
// Accelerometer
slouken@4964
   167
extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
slouken@4998
   168
                                    JNIEnv* env, jclass jcls,
slouken@4995
   169
                                    jfloat x, jfloat y, jfloat z)
slouken@4964
   170
{
slouken@4964
   171
    fLastAccelerometer[0] = x;
slouken@4964
   172
    fLastAccelerometer[1] = y;
slouken@6212
   173
    fLastAccelerometer[2] = z;
slouken@6212
   174
    bHasNewData = true;
slouken@4964
   175
}
slouken@4964
   176
slouken@4995
   177
// Quit
slouken@4995
   178
extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
slouken@4998
   179
                                    JNIEnv* env, jclass cls)
slouken@4995
   180
{    
slouken@4995
   181
    // Inject a SDL_QUIT event
slouken@4995
   182
    SDL_SendQuit();
slouken@4995
   183
}
slouken@4995
   184
slouken@6186
   185
// Pause
slouken@6186
   186
extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
slouken@6186
   187
                                    JNIEnv* env, jclass cls)
slouken@6186
   188
{
slouken@6186
   189
    if (Android_Window) {
gabomdq@6330
   190
        /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
gabomdq@6330
   191
        if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
slouken@6186
   192
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
slouken@6191
   193
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
slouken@6186
   194
    }
slouken@6186
   195
}
slouken@6186
   196
slouken@6186
   197
// Resume
slouken@6186
   198
extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
slouken@6186
   199
                                    JNIEnv* env, jclass cls)
slouken@6186
   200
{
slouken@6186
   201
    if (Android_Window) {
gabomdq@6330
   202
        /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
gabomdq@6330
   203
         * We can't restore the GL Context here because it needs to be done on the SDL main thread
gabomdq@6330
   204
         * and this function will be called from the Java thread instead.
gabomdq@6330
   205
         */
gabomdq@6330
   206
        if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
slouken@6186
   207
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
slouken@6191
   208
        SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
slouken@6186
   209
    }
slouken@6186
   210
}
slouken@6186
   211
slouken@4995
   212
extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
slouken@4998
   213
                                    JNIEnv* env, jclass cls)
slouken@4995
   214
{
slouken@4998
   215
    /* This is the audio thread, with a different environment */
gabomdq@6354
   216
    Android_JNI_SetupThread();
slouken@4998
   217
slouken@4996
   218
    Android_RunAudioThread();
slouken@4995
   219
}
slouken@4964
   220
slouken@6555
   221
extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
slouken@6555
   222
                                    JNIEnv* env, jclass cls,
slouken@6555
   223
                                    jstring text, jint newCursorPosition)
slouken@6555
   224
{
slouken@6555
   225
    const char *utftext = env->GetStringUTFChars(text, NULL);
slouken@6555
   226
slouken@6555
   227
    SDL_SendKeyboardText(utftext);
slouken@6555
   228
slouken@6555
   229
    env->ReleaseStringUTFChars(text, utftext);
slouken@6555
   230
}
slouken@6555
   231
slouken@6555
   232
extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
slouken@6555
   233
                                    JNIEnv* env, jclass cls,
slouken@6555
   234
                                    jstring text, jint newCursorPosition)
slouken@6555
   235
{
slouken@6555
   236
    const char *utftext = env->GetStringUTFChars(text, NULL);
slouken@6555
   237
slouken@6555
   238
    SDL_SendEditingText(utftext, 0, 0);
slouken@6555
   239
slouken@6555
   240
    env->ReleaseStringUTFChars(text, utftext);
slouken@6555
   241
}
slouken@6555
   242
slouken@6555
   243
slouken@6555
   244
slouken@4964
   245
slouken@4964
   246
/*******************************************************************************
slouken@4964
   247
             Functions called by SDL into Java
slouken@4964
   248
*******************************************************************************/
slouken@6284
   249
slouken@6284
   250
class LocalReferenceHolder
slouken@6284
   251
{
slouken@6284
   252
private:
slouken@6284
   253
    static int s_active;
slouken@6284
   254
slouken@6284
   255
public:
slouken@6284
   256
    static bool IsActive() {
slouken@6284
   257
        return s_active > 0;
slouken@6284
   258
    }
slouken@6284
   259
slouken@6284
   260
public:
slouken@6284
   261
    LocalReferenceHolder() : m_env(NULL) { }
slouken@6284
   262
    ~LocalReferenceHolder() {
slouken@6284
   263
        if (m_env) {
slouken@6284
   264
            m_env->PopLocalFrame(NULL);
slouken@6284
   265
            --s_active;
slouken@6284
   266
        }
slouken@6284
   267
    }
slouken@6284
   268
slouken@6284
   269
    bool init(JNIEnv *env, jint capacity = 16) {
slouken@6284
   270
        if (env->PushLocalFrame(capacity) < 0) {
slouken@6284
   271
            SDL_SetError("Failed to allocate enough JVM local references");
slouken@6284
   272
            return false;
slouken@6284
   273
        }
slouken@6284
   274
        ++s_active;
slouken@6284
   275
        m_env = env;
slouken@6284
   276
        return true;
slouken@6284
   277
    }
slouken@6284
   278
slouken@6284
   279
protected:
slouken@6284
   280
    JNIEnv *m_env;
slouken@6284
   281
};
slouken@6284
   282
int LocalReferenceHolder::s_active;
slouken@6284
   283
slouken@5222
   284
extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
slouken@4964
   285
{
gabomdq@6354
   286
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@5222
   287
    if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
slouken@5222
   288
        return SDL_TRUE;
slouken@5222
   289
    } else {
slouken@5222
   290
        return SDL_FALSE;
slouken@5222
   291
    }
slouken@4964
   292
}
slouken@4964
   293
slouken@4989
   294
extern "C" void Android_JNI_SwapWindow()
slouken@4964
   295
{
gabomdq@6354
   296
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@4998
   297
    mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers); 
slouken@4998
   298
}
slouken@4998
   299
slouken@4998
   300
extern "C" void Android_JNI_SetActivityTitle(const char *title)
slouken@4998
   301
{
slouken@4998
   302
    jmethodID mid;
gabomdq@6354
   303
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@4998
   304
    mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
slouken@4998
   305
    if (mid) {
gabomdq@6307
   306
        jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
gabomdq@6307
   307
        mEnv->CallStaticVoidMethod(mActivityClass, mid, jtitle);
gabomdq@6307
   308
        mEnv->DeleteLocalRef(jtitle);
slouken@4998
   309
    }
slouken@4964
   310
}
slouken@4964
   311
slouken@6212
   312
extern "C" SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
slouken@5000
   313
{
slouken@5000
   314
    int i;
slouken@6212
   315
    SDL_bool retval = SDL_FALSE;
slouken@6212
   316
slouken@6212
   317
    if (bHasNewData) {
slouken@6212
   318
        for (i = 0; i < 3; ++i) {
slouken@6212
   319
            values[i] = fLastAccelerometer[i];
slouken@6212
   320
        }
slouken@6212
   321
        bHasNewData = false;
slouken@6212
   322
        retval = SDL_TRUE;
slouken@5000
   323
    }
slouken@6212
   324
slouken@6212
   325
    return retval;
slouken@5000
   326
}
slouken@5000
   327
gabomdq@6354
   328
static void Android_JNI_ThreadDestroyed(void* value) {
gabomdq@6354
   329
    /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
gabomdq@6354
   330
    JNIEnv *env = (JNIEnv*) value;
gabomdq@6354
   331
    if (env != NULL) {
gabomdq@6354
   332
        mJavaVM->DetachCurrentThread();
gabomdq@6354
   333
        pthread_setspecific(mThreadKey, NULL);
gabomdq@6354
   334
    }
gabomdq@6354
   335
}
gabomdq@6354
   336
gabomdq@6354
   337
JNIEnv* Android_JNI_GetEnv(void) {
gabomdq@6354
   338
    /* From http://developer.android.com/guide/practices/jni.html
gabomdq@6354
   339
     * All threads are Linux threads, scheduled by the kernel.
gabomdq@6354
   340
     * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
gabomdq@6354
   341
     * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
gabomdq@6354
   342
     * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
gabomdq@6354
   343
     * and cannot make JNI calls.
gabomdq@6354
   344
     * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
gabomdq@6354
   345
     * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
gabomdq@6354
   346
     * is a no-op.
gabomdq@6354
   347
     * Note: You can call this function any number of times for the same thread, there's no harm in it
gabomdq@6354
   348
     */
gabomdq@6354
   349
gabomdq@6354
   350
    JNIEnv *env;
gabomdq@6354
   351
    int status = mJavaVM->AttachCurrentThread(&env, NULL);
gabomdq@6354
   352
    if(status < 0) {
gabomdq@6354
   353
        LOGE("failed to attach current thread");
gabomdq@6354
   354
        return 0;
gabomdq@6354
   355
    }
gabomdq@6354
   356
gabomdq@6354
   357
    return env;
gabomdq@6354
   358
}
gabomdq@6354
   359
gabomdq@6354
   360
int Android_JNI_SetupThread(void) {
gabomdq@6354
   361
    /* From http://developer.android.com/guide/practices/jni.html
gabomdq@6354
   362
     * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
gabomdq@6354
   363
     * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
gabomdq@6354
   364
     * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
gabomdq@6354
   365
     * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
gabomdq@6354
   366
     * Note: The destructor is not called unless the stored value is != NULL
gabomdq@6354
   367
     * Note: You can call this function any number of times for the same thread, there's no harm in it
gabomdq@6354
   368
     *       (except for some lost CPU cycles)
gabomdq@6354
   369
     */
gabomdq@6354
   370
    JNIEnv *env = Android_JNI_GetEnv();
gabomdq@6354
   371
    pthread_setspecific(mThreadKey, (void*) env);
gabomdq@6354
   372
    return 1;
gabomdq@6354
   373
}
gabomdq@6354
   374
slouken@4995
   375
//
slouken@4995
   376
// Audio support
slouken@4995
   377
//
slouken@4996
   378
static jboolean audioBuffer16Bit = JNI_FALSE;
slouken@4996
   379
static jboolean audioBufferStereo = JNI_FALSE;
slouken@4996
   380
static jobject audioBuffer = NULL;
slouken@4996
   381
static void* audioBufferPinned = NULL;
slouken@4995
   382
slouken@4995
   383
extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
slouken@4964
   384
{
slouken@4996
   385
    int audioBufferFrames;
slouken@4964
   386
icculus@5996
   387
    int status;
gabomdq@6354
   388
    JNIEnv *env = Android_JNI_GetEnv();
gabomdq@6354
   389
gabomdq@6354
   390
    if (!env) {
gabomdq@6354
   391
        LOGE("callback_handler: failed to attach current thread");
icculus@5996
   392
    }
gabomdq@6354
   393
    Android_JNI_SetupThread();
icculus@5996
   394
icculus@5996
   395
    
slouken@4996
   396
    __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
slouken@4996
   397
    audioBuffer16Bit = is16Bit;
slouken@4996
   398
    audioBufferStereo = channelCount > 1;
slouken@4964
   399
icculus@5996
   400
    audioBuffer = env->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
slouken@4995
   401
slouken@4996
   402
    if (audioBuffer == NULL) {
slouken@4996
   403
        __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
slouken@4996
   404
        return 0;
slouken@4996
   405
    }
icculus@5996
   406
    audioBuffer = env->NewGlobalRef(audioBuffer);
slouken@4995
   407
slouken@4996
   408
    jboolean isCopy = JNI_FALSE;
slouken@4996
   409
    if (audioBuffer16Bit) {
icculus@5996
   410
        audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
icculus@5996
   411
        audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
slouken@4996
   412
    } else {
icculus@5996
   413
        audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
icculus@5996
   414
        audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
slouken@4996
   415
    }
slouken@4996
   416
    if (audioBufferStereo) {
slouken@4996
   417
        audioBufferFrames /= 2;
slouken@4996
   418
    }
icculus@5996
   419
 
slouken@4996
   420
    return audioBufferFrames;
slouken@4995
   421
}
slouken@4995
   422
slouken@4996
   423
extern "C" void * Android_JNI_GetAudioBuffer()
slouken@4995
   424
{
slouken@4996
   425
    return audioBufferPinned;
slouken@4995
   426
}
slouken@4995
   427
slouken@4996
   428
extern "C" void Android_JNI_WriteAudioBuffer()
slouken@4995
   429
{
gabomdq@6354
   430
    JNIEnv *mAudioEnv = Android_JNI_GetEnv();
gabomdq@6354
   431
slouken@4996
   432
    if (audioBuffer16Bit) {
slouken@4996
   433
        mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
slouken@4998
   434
        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
slouken@4996
   435
    } else {
slouken@4996
   436
        mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
slouken@4998
   437
        mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
slouken@4996
   438
    }
slouken@4995
   439
slouken@4996
   440
    /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
slouken@4995
   441
}
slouken@4995
   442
slouken@4995
   443
extern "C" void Android_JNI_CloseAudioDevice()
slouken@4995
   444
{
icculus@5996
   445
    int status;
gabomdq@6354
   446
    JNIEnv *env = Android_JNI_GetEnv();
icculus@5996
   447
icculus@5996
   448
    env->CallStaticVoidMethod(mActivityClass, midAudioQuit); 
slouken@4964
   449
slouken@4997
   450
    if (audioBuffer) {
icculus@5996
   451
        env->DeleteGlobalRef(audioBuffer);
slouken@4997
   452
        audioBuffer = NULL;
slouken@4997
   453
        audioBufferPinned = NULL;
slouken@4997
   454
    }
slouken@4964
   455
}
slouken@4981
   456
tim@5650
   457
// Test for an exception and call SDL_SetError with its detail if one occurs
tim@5650
   458
static bool Android_JNI_ExceptionOccurred()
tim@5650
   459
{
slouken@6284
   460
    SDL_assert(LocalReferenceHolder::IsActive());
gabomdq@6354
   461
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@6284
   462
tim@5650
   463
    jthrowable exception = mEnv->ExceptionOccurred();
tim@5650
   464
    if (exception != NULL) {
tim@5650
   465
        jmethodID mid;
tim@5650
   466
tim@5650
   467
        // Until this happens most JNI operations have undefined behaviour
tim@5650
   468
        mEnv->ExceptionClear();
tim@5650
   469
tim@5650
   470
        jclass exceptionClass = mEnv->GetObjectClass(exception);
tim@5650
   471
        jclass classClass = mEnv->FindClass("java/lang/Class");
tim@5650
   472
tim@5650
   473
        mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
tim@5650
   474
        jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
tim@5650
   475
        const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
tim@5650
   476
tim@5650
   477
        mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
icculus@5860
   478
        jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
tim@5650
   479
tim@5650
   480
        if (exceptionMessage != NULL) {
tim@5650
   481
            const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
tim@5650
   482
                    exceptionMessage, 0);
tim@5650
   483
            SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
tim@5650
   484
            mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
tim@5650
   485
        } else {
tim@5650
   486
            SDL_SetError("%s", exceptionNameUTF8);
tim@5650
   487
        }
tim@5650
   488
tim@5650
   489
        mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
tim@5650
   490
tim@5650
   491
        return true;
tim@5650
   492
    }
tim@5650
   493
tim@5650
   494
    return false;
tim@5650
   495
}
tim@5650
   496
icculus@5582
   497
static int Android_JNI_FileOpen(SDL_RWops* ctx)
icculus@5582
   498
{
slouken@6284
   499
    LocalReferenceHolder refs;
tim@5650
   500
    int result = 0;
tim@5650
   501
tim@5650
   502
    jmethodID mid;
tim@5650
   503
    jobject context;
tim@5650
   504
    jobject assetManager;
tim@5650
   505
    jobject inputStream;
tim@5650
   506
    jclass channels;
tim@5650
   507
    jobject readableByteChannel;
tim@5650
   508
    jstring fileNameJString;
tim@5650
   509
gabomdq@6354
   510
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@6284
   511
    if (!refs.init(mEnv)) {
tim@5650
   512
        goto failure;
tim@5650
   513
    }
tim@5650
   514
gabomdq@6308
   515
    fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
icculus@5582
   516
icculus@5582
   517
    // context = SDLActivity.getContext();
tim@5650
   518
    mid = mEnv->GetStaticMethodID(mActivityClass,
icculus@5582
   519
            "getContext","()Landroid/content/Context;");
tim@5650
   520
    context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
icculus@5582
   521
icculus@5582
   522
    // assetManager = context.getAssets();
icculus@5582
   523
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
tim@5650
   524
            "getAssets", "()Landroid/content/res/AssetManager;");
tim@5650
   525
    assetManager = mEnv->CallObjectMethod(context, mid);
icculus@5582
   526
icculus@5582
   527
    // inputStream = assetManager.open(<filename>);
icculus@5582
   528
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
icculus@5582
   529
            "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
tim@5650
   530
    inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
tim@5650
   531
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   532
        goto failure;
icculus@5582
   533
    }
icculus@5582
   534
tim@5650
   535
    ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
tim@5650
   536
icculus@5582
   537
    // Despite all the visible documentation on [Asset]InputStream claiming
icculus@5582
   538
    // that the .available() method is not guaranteed to return the entire file
icculus@5582
   539
    // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
icculus@5582
   540
    // android/apis/content/ReadAsset.java imply that Android's
icculus@5582
   541
    // AssetInputStream.available() /will/ always return the total file size
icculus@5582
   542
icculus@5582
   543
    // size = inputStream.available();
icculus@5582
   544
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
icculus@5582
   545
            "available", "()I");
icculus@5582
   546
    ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
tim@5650
   547
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   548
        goto failure;
icculus@5582
   549
    }
icculus@5582
   550
icculus@5582
   551
    // readableByteChannel = Channels.newChannel(inputStream);
tim@5650
   552
    channels = mEnv->FindClass("java/nio/channels/Channels");
icculus@5582
   553
    mid = mEnv->GetStaticMethodID(channels,
icculus@5582
   554
            "newChannel",
icculus@5582
   555
            "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
tim@5650
   556
    readableByteChannel = mEnv->CallStaticObjectMethod(
icculus@5582
   557
            channels, mid, inputStream);
tim@5650
   558
    if (Android_JNI_ExceptionOccurred()) {
tim@5650
   559
        goto failure;
icculus@5582
   560
    }
icculus@5582
   561
tim@5650
   562
    ctx->hidden.androidio.readableByteChannelRef =
tim@5650
   563
        mEnv->NewGlobalRef(readableByteChannel);
tim@5650
   564
icculus@5582
   565
    // Store .read id for reading purposes
icculus@5582
   566
    mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
icculus@5582
   567
            "read", "(Ljava/nio/ByteBuffer;)I");
icculus@5582
   568
    ctx->hidden.androidio.readMethod = mid;
icculus@5582
   569
icculus@5582
   570
    ctx->hidden.androidio.position = 0;
icculus@5582
   571
tim@5650
   572
    if (false) {
tim@5650
   573
failure:
tim@5650
   574
        result = -1;
tim@5650
   575
tim@5650
   576
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
tim@5650
   577
tim@5650
   578
        if(ctx->hidden.androidio.inputStreamRef != NULL) {
tim@5650
   579
            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
tim@5650
   580
        }
gabomdq@6308
   581
gabomdq@6308
   582
        if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
gabomdq@6308
   583
            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
gabomdq@6308
   584
        }
gabomdq@6308
   585
tim@5650
   586
    }
tim@5650
   587
tim@5650
   588
    return result;
icculus@5582
   589
}
icculus@5582
   590
icculus@5582
   591
extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
icculus@5582
   592
        const char* fileName, const char*)
icculus@5582
   593
{
slouken@6284
   594
    LocalReferenceHolder refs;
gabomdq@6354
   595
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@6284
   596
slouken@6284
   597
    if (!refs.init(mEnv)) {
slouken@6284
   598
        return -1;
slouken@6284
   599
    }
slouken@6284
   600
icculus@5582
   601
    if (!ctx) {
icculus@5582
   602
        return -1;
icculus@5582
   603
    }
icculus@5582
   604
icculus@5582
   605
    jstring fileNameJString = mEnv->NewStringUTF(fileName);
icculus@5582
   606
    ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
tim@5650
   607
    ctx->hidden.androidio.inputStreamRef = NULL;
gabomdq@6335
   608
    ctx->hidden.androidio.readableByteChannelRef = NULL;
gabomdq@6335
   609
    ctx->hidden.androidio.readMethod = NULL;
icculus@5582
   610
icculus@5582
   611
    return Android_JNI_FileOpen(ctx);
icculus@5582
   612
}
icculus@5582
   613
icculus@5582
   614
extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
icculus@5582
   615
        size_t size, size_t maxnum)
icculus@5582
   616
{
slouken@6284
   617
    LocalReferenceHolder refs;
gabomdq@6377
   618
    jlong bytesRemaining = (jlong) (size * maxnum);
gabomdq@6377
   619
    jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
icculus@5582
   620
    int bytesRead = 0;
icculus@5582
   621
gabomdq@6377
   622
    /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
gabomdq@6377
   623
    if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
gabomdq@6377
   624
gabomdq@6354
   625
    JNIEnv *mEnv = Android_JNI_GetEnv();
slouken@6284
   626
    if (!refs.init(mEnv)) {
slouken@6284
   627
        return -1;
slouken@6284
   628
    }
slouken@6284
   629
gabomdq@6308
   630
    jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
icculus@5582
   631
    jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
icculus@5582
   632
    jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
icculus@5582
   633
icculus@5582
   634
    while (bytesRemaining > 0) {
icculus@5582
   635
        // result = readableByteChannel.read(...);
icculus@5582
   636
        int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
icculus@5582
   637
tim@5650
   638
        if (Android_JNI_ExceptionOccurred()) {
icculus@5582
   639
            return 0;
icculus@5582
   640
        }
icculus@5582
   641
icculus@5582
   642
        if (result < 0) {
icculus@5582
   643
            break;
icculus@5582
   644
        }
icculus@5582
   645
icculus@5582
   646
        bytesRemaining -= result;
icculus@5582
   647
        bytesRead += result;
icculus@5582
   648
        ctx->hidden.androidio.position += result;
icculus@5582
   649
    }
icculus@5582
   650
icculus@5582
   651
    return bytesRead / size;
icculus@5582
   652
}
icculus@5582
   653
icculus@5582
   654
extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
icculus@5582
   655
        size_t size, size_t num)
icculus@5582
   656
{
icculus@5582
   657
    SDL_SetError("Cannot write to Android package filesystem");
icculus@5582
   658
    return 0;
icculus@5582
   659
}
icculus@5582
   660
icculus@5582
   661
static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
icculus@5582
   662
{
slouken@6284
   663
    LocalReferenceHolder refs;
icculus@5582
   664
    int result = 0;
gabomdq@6354
   665
    JNIEnv *mEnv = Android_JNI_GetEnv();
icculus@5582
   666
slouken@6284
   667
    if (!refs.init(mEnv)) {
slouken@6284
   668
        SDL_SetError("Failed to allocate enough JVM local references");
slouken@6284
   669
        return -1;
slouken@6284
   670
    }
slouken@6284
   671
icculus@5582
   672
    if (ctx) {
icculus@5582
   673
        if (release) {
icculus@5582
   674
            mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
icculus@5582
   675
        }
icculus@5582
   676
gabomdq@6308
   677
        jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
icculus@5582
   678
icculus@5582
   679
        // inputStream.close();
icculus@5582
   680
        jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
icculus@5582
   681
                "close", "()V");
icculus@5582
   682
        mEnv->CallVoidMethod(inputStream, mid);
icculus@5582
   683
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
icculus@5582
   684
        mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
tim@5650
   685
        if (Android_JNI_ExceptionOccurred()) {
icculus@5582
   686
            result = -1;
icculus@5582
   687
        }
icculus@5582
   688
icculus@5582
   689
        if (release) {
icculus@5582
   690
            SDL_FreeRW(ctx);
icculus@5582
   691
        }
icculus@5582
   692
    }
icculus@5582
   693
icculus@5582
   694
    return result;
icculus@5582
   695
}
icculus@5582
   696
icculus@5582
   697
icculus@5582
   698
extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
icculus@5582
   699
{
icculus@5582
   700
    long newPosition;
icculus@5582
   701
icculus@5582
   702
    switch (whence) {
icculus@5582
   703
        case RW_SEEK_SET:
icculus@5582
   704
            newPosition = offset;
icculus@5582
   705
            break;
icculus@5582
   706
        case RW_SEEK_CUR:
icculus@5582
   707
            newPosition = ctx->hidden.androidio.position + offset;
icculus@5582
   708
            break;
icculus@5582
   709
        case RW_SEEK_END:
icculus@5582
   710
            newPosition = ctx->hidden.androidio.size + offset;
icculus@5582
   711
            break;
icculus@5582
   712
        default:
icculus@5582
   713
            SDL_SetError("Unknown value for 'whence'");
icculus@5582
   714
            return -1;
icculus@5582
   715
    }
icculus@5582
   716
    if (newPosition < 0) {
icculus@5582
   717
        newPosition = 0;
icculus@5582
   718
    }
icculus@5582
   719
    if (newPosition > ctx->hidden.androidio.size) {
icculus@5582
   720
        newPosition = ctx->hidden.androidio.size;
icculus@5582
   721
    }
icculus@5582
   722
icculus@5582
   723
    long movement = newPosition - ctx->hidden.androidio.position;
gabomdq@6308
   724
    jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
icculus@5582
   725
icculus@5582
   726
    if (movement > 0) {
tim@5993
   727
        unsigned char buffer[1024];
tim@5993
   728
icculus@5582
   729
        // The easy case where we're seeking forwards
icculus@5582
   730
        while (movement > 0) {
icculus@5994
   731
            long amount = (long) sizeof (buffer);
icculus@5994
   732
            if (amount > movement) {
icculus@5994
   733
                amount = movement;
icculus@5994
   734
            }
icculus@5994
   735
            size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
tim@5993
   736
tim@5993
   737
            if (result <= 0) {
tim@5993
   738
                // Failed to read/skip the required amount, so fail
icculus@5582
   739
                return -1;
icculus@5582
   740
            }
tim@5993
   741
tim@5993
   742
            movement -= result;
icculus@5582
   743
        }
icculus@5582
   744
    } else if (movement < 0) {
icculus@5582
   745
        // We can't seek backwards so we have to reopen the file and seek
icculus@5582
   746
        // forwards which obviously isn't very efficient
icculus@5582
   747
        Android_JNI_FileClose(ctx, false);
icculus@5582
   748
        Android_JNI_FileOpen(ctx);
icculus@5582
   749
        Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
icculus@5582
   750
    }
icculus@5582
   751
icculus@5582
   752
    ctx->hidden.androidio.position = newPosition;
icculus@5582
   753
icculus@5582
   754
    return ctx->hidden.androidio.position;
icculus@5582
   755
}
icculus@5582
   756
icculus@5582
   757
extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
icculus@5582
   758
{
icculus@5582
   759
    return Android_JNI_FileClose(ctx, true);
icculus@5582
   760
}
icculus@5582
   761
slouken@6464
   762
// returns a new global reference which needs to be released later
slouken@6464
   763
static jobject Android_JNI_GetSystemServiceObject(const char* name)
slouken@6464
   764
{
slouken@6464
   765
    LocalReferenceHolder refs;
slouken@6464
   766
    JNIEnv* env = Android_JNI_GetEnv();
slouken@6464
   767
    if (!refs.init(env)) {
slouken@6464
   768
        return NULL;
slouken@6464
   769
    }
slouken@6464
   770
slouken@6464
   771
    jstring service = env->NewStringUTF(name);
slouken@6464
   772
slouken@6464
   773
    jmethodID mid;
slouken@6464
   774
slouken@6464
   775
    mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
slouken@6464
   776
    jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
slouken@6464
   777
slouken@6464
   778
    mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
slouken@6464
   779
    jobject manager = env->CallObjectMethod(context, mid, service);
slouken@6464
   780
slouken@6464
   781
    env->DeleteLocalRef(service);
slouken@6464
   782
slouken@6464
   783
    return manager ? env->NewGlobalRef(manager) : NULL;
slouken@6464
   784
}
slouken@6464
   785
slouken@6464
   786
#define SETUP_CLIPBOARD(error) \
slouken@6464
   787
    LocalReferenceHolder refs; \
slouken@6464
   788
    JNIEnv* env = Android_JNI_GetEnv(); \
slouken@6464
   789
    if (!refs.init(env)) { \
slouken@6464
   790
        return error; \
slouken@6464
   791
    } \
slouken@6464
   792
    jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
slouken@6464
   793
    if (!clipboard) { \
slouken@6464
   794
        return error; \
slouken@6464
   795
    }
slouken@6464
   796
slouken@6464
   797
extern "C" int Android_JNI_SetClipboardText(const char* text)
slouken@6464
   798
{
slouken@6464
   799
    SETUP_CLIPBOARD(-1)
slouken@6464
   800
slouken@6464
   801
    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
slouken@6464
   802
    jstring string = env->NewStringUTF(text);
slouken@6464
   803
    env->CallVoidMethod(clipboard, mid, string);
slouken@6464
   804
    env->DeleteGlobalRef(clipboard);
slouken@6464
   805
    env->DeleteLocalRef(string);
slouken@6464
   806
    return 0;
slouken@6464
   807
}
slouken@6464
   808
slouken@6464
   809
extern "C" char* Android_JNI_GetClipboardText()
slouken@6464
   810
{
slouken@6464
   811
    SETUP_CLIPBOARD(SDL_strdup(""))
slouken@6464
   812
slouken@6464
   813
    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
slouken@6464
   814
    jobject sequence = env->CallObjectMethod(clipboard, mid);
slouken@6464
   815
    env->DeleteGlobalRef(clipboard);
slouken@6464
   816
    if (sequence) {
slouken@6464
   817
        mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
slouken@6464
   818
        jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
slouken@6464
   819
        const char* utf = env->GetStringUTFChars(string, 0);
slouken@6464
   820
        if (utf) {
slouken@6464
   821
            char* text = SDL_strdup(utf);
slouken@6464
   822
            env->ReleaseStringUTFChars(string, utf);
slouken@6464
   823
            return text;
slouken@6464
   824
        }
slouken@6464
   825
    }
slouken@6464
   826
    return SDL_strdup("");
slouken@6464
   827
}
slouken@6464
   828
slouken@6464
   829
extern "C" SDL_bool Android_JNI_HasClipboardText()
slouken@6464
   830
{
slouken@6464
   831
    SETUP_CLIPBOARD(SDL_FALSE)
slouken@6464
   832
slouken@6464
   833
    jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
slouken@6464
   834
    jboolean has = env->CallBooleanMethod(clipboard, mid);
slouken@6464
   835
    env->DeleteGlobalRef(clipboard);
slouken@6464
   836
    return has ? SDL_TRUE : SDL_FALSE;
slouken@6464
   837
}
slouken@6464
   838
slouken@6464
   839
slouken@6448
   840
// returns 0 on success or -1 on error (others undefined then)
slouken@6448
   841
// returns truthy or falsy value in plugged, charged and battery
slouken@6448
   842
// returns the value in seconds and percent or -1 if not available
slouken@6448
   843
extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
slouken@6448
   844
{
slouken@6448
   845
    LocalReferenceHolder refs;
slouken@6448
   846
    JNIEnv* env = Android_JNI_GetEnv();
slouken@6448
   847
    if (!refs.init(env)) {
slouken@6448
   848
        return -1;
slouken@6448
   849
    }
slouken@6448
   850
slouken@6448
   851
    jmethodID mid;
slouken@6448
   852
slouken@6448
   853
    mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
slouken@6448
   854
    jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
slouken@6448
   855
slouken@6448
   856
    jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
slouken@6448
   857
slouken@6448
   858
    jclass cls = env->FindClass("android/content/IntentFilter");
slouken@6448
   859
slouken@6448
   860
    mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
slouken@6448
   861
    jobject filter = env->NewObject(cls, mid, action);
slouken@6448
   862
slouken@6448
   863
    env->DeleteLocalRef(action);
slouken@6448
   864
slouken@6448
   865
    mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
slouken@6448
   866
    jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
slouken@6448
   867
slouken@6448
   868
    env->DeleteLocalRef(filter);
slouken@6448
   869
slouken@6448
   870
    cls = env->GetObjectClass(intent);
slouken@6448
   871
slouken@6448
   872
    jstring iname;
slouken@6448
   873
    jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
slouken@6448
   874
slouken@6448
   875
#define GET_INT_EXTRA(var, key) \
slouken@6448
   876
    iname = env->NewStringUTF(key); \
slouken@6448
   877
    int var = env->CallIntMethod(intent, imid, iname, -1); \
slouken@6448
   878
    env->DeleteLocalRef(iname);
slouken@6448
   879
slouken@6448
   880
    jstring bname;
slouken@6448
   881
    jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
slouken@6448
   882
slouken@6448
   883
#define GET_BOOL_EXTRA(var, key) \
slouken@6448
   884
    bname = env->NewStringUTF(key); \
slouken@6448
   885
    int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
slouken@6448
   886
    env->DeleteLocalRef(bname);
slouken@6448
   887
slouken@6448
   888
    if (plugged) {
slouken@6448
   889
        GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
slouken@6448
   890
        if (plug == -1) {
slouken@6448
   891
            return -1;
slouken@6448
   892
        }
slouken@6448
   893
        // 1 == BatteryManager.BATTERY_PLUGGED_AC
slouken@6448
   894
        // 2 == BatteryManager.BATTERY_PLUGGED_USB
slouken@6448
   895
        *plugged = (0 < plug) ? 1 : 0;
slouken@6448
   896
    }
slouken@6448
   897
slouken@6448
   898
    if (charged) {
slouken@6448
   899
        GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
slouken@6448
   900
        if (status == -1) {
slouken@6448
   901
            return -1;
slouken@6448
   902
        }
slouken@6448
   903
        // 5 == BatteryManager.BATTERY_STATUS_FULL
slouken@6448
   904
        *charged = (status == 5) ? 1 : 0;
slouken@6448
   905
    }
slouken@6448
   906
slouken@6448
   907
    if (battery) {
slouken@6448
   908
        GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
slouken@6448
   909
        *battery = present ? 1 : 0;
slouken@6448
   910
    }
slouken@6448
   911
slouken@6448
   912
    if (seconds) {
slouken@6448
   913
        *seconds = -1; // not possible
slouken@6448
   914
    }
slouken@6448
   915
slouken@6448
   916
    if (percent) {
slouken@6448
   917
        GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
slouken@6448
   918
        GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
slouken@6448
   919
        if ((level == -1) || (scale == -1)) {
slouken@6448
   920
            return -1;
slouken@6448
   921
        }
slouken@6448
   922
        *percent = level * 100 / scale;
slouken@6448
   923
    }
slouken@6448
   924
slouken@6448
   925
    env->DeleteLocalRef(intent);
slouken@6448
   926
slouken@6448
   927
    return 0;
slouken@6448
   928
}
slouken@6448
   929
slouken@6392
   930
// sends message to be handled on the UI event dispatch thread
slouken@6392
   931
extern "C" int Android_JNI_SendMessage(int command, int param)
slouken@6392
   932
{
slouken@6392
   933
    JNIEnv *env = Android_JNI_GetEnv();
slouken@6392
   934
    if (!env) {
slouken@6392
   935
        return -1;
slouken@6392
   936
    }
slouken@6392
   937
    jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)V");
slouken@6392
   938
    if (!mid) {
slouken@6392
   939
        return -1;
slouken@6392
   940
    }
slouken@6392
   941
    env->CallStaticVoidMethod(mActivityClass, mid, command, param);
slouken@6392
   942
    return 0;
slouken@6392
   943
}
slouken@6392
   944
slouken@6555
   945
extern "C" int Android_JNI_ShowTextInput(SDL_Rect *inputRect)
slouken@6555
   946
{
slouken@6555
   947
    JNIEnv *env = Android_JNI_GetEnv();
slouken@6555
   948
    if (!env) {
slouken@6555
   949
        return -1;
slouken@6555
   950
    }
slouken@6555
   951
slouken@6555
   952
    jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)V");
slouken@6555
   953
    if (!mid) {
slouken@6555
   954
        return -1;
slouken@6555
   955
    }
slouken@6555
   956
    env->CallStaticVoidMethod( mActivityClass, mid,
slouken@6555
   957
                               inputRect->x,
slouken@6555
   958
                               inputRect->y,
slouken@6555
   959
                               inputRect->w,
slouken@6555
   960
                               inputRect->h );
slouken@6555
   961
    return 0;
slouken@6555
   962
}
slouken@6555
   963
slouken@6555
   964
/*extern "C" int Android_JNI_HideTextInput()
slouken@6555
   965
{
slouken@6555
   966
slouken@6555
   967
slouken@6555
   968
    JNIEnv *env = Android_JNI_GetEnv();
slouken@6555
   969
    if (!env) {
slouken@6555
   970
        return -1;
slouken@6555
   971
    }
slouken@6555
   972
slouken@6555
   973
    jmethodID mid = env->GetStaticMethodID(mActivityClass, "hideTextInput", "()V");
slouken@6555
   974
    if (!mid) {
slouken@6555
   975
        return -1;
slouken@6555
   976
    }
slouken@6555
   977
    env->CallStaticVoidMethod(mActivityClass, mid);
slouken@6555
   978
    return 0;
slouken@6555
   979
}*/
slouken@6555
   980
slouken@6044
   981
#endif /* __ANDROID__ */
slouken@6044
   982
slouken@4981
   983
/* vi: set ts=4 sw=4 expandtab: */