| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "SDL_config.h" |
| #include "SDL_stdinc.h" |
| #include "SDL_assert.h" |
| #include "SDL_log.h" |
| |
| #ifdef __ANDROID__ |
| |
| #include "SDL_system.h" |
| #include "SDL_android.h" |
| #include <EGL/egl.h> |
| |
| #include "../../events/SDL_events_c.h" |
| #include "../../video/android/SDL_androidkeyboard.h" |
| #include "../../video/android/SDL_androidtouch.h" |
| #include "../../video/android/SDL_androidvideo.h" |
| |
| #include <android/log.h> |
| #include <pthread.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #define LOG_TAG "SDL_android" |
| //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) |
| //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) |
| #define LOGI(...) do {} while (false) |
| #define LOGE(...) do {} while (false) |
| |
| /* Uncomment this to log messages entering and exiting methods in this file */ |
| //#define DEBUG_JNI |
| |
| static void Android_JNI_ThreadDestroyed(void*); |
| |
| /******************************************************************************* |
| This file links the Java side of Android with libsdl |
| *******************************************************************************/ |
| #include <jni.h> |
| #include <android/log.h> |
| #include <stdbool.h> |
| |
| |
| /******************************************************************************* |
| Globals |
| *******************************************************************************/ |
| static pthread_key_t mThreadKey; |
| static JavaVM* mJavaVM; |
| |
| // Main activity |
| static jclass mActivityClass; |
| |
| // method signatures |
| static jmethodID midCreateGLContext; |
| static jmethodID midDeleteGLContext; |
| static jmethodID midFlipBuffers; |
| static jmethodID midAudioInit; |
| static jmethodID midAudioWriteShortBuffer; |
| static jmethodID midAudioWriteByteBuffer; |
| static jmethodID midAudioQuit; |
| |
| // Accelerometer data storage |
| static float fLastAccelerometer[3]; |
| static bool bHasNewData; |
| |
| /******************************************************************************* |
| Functions called by JNI |
| *******************************************************************************/ |
| |
| // Library init |
| jint JNI_OnLoad(JavaVM* vm, void* reserved) |
| { |
| JNIEnv *env; |
| mJavaVM = vm; |
| LOGI("JNI_OnLoad called"); |
| if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) { |
| LOGE("Failed to get the environment using GetEnv()"); |
| return -1; |
| } |
| /* |
| * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread |
| * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this |
| */ |
| if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) { |
| __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key"); |
| } |
| else { |
| Android_JNI_SetupThread(); |
| } |
| |
| return JNI_VERSION_1_4; |
| } |
| |
| // Called before SDL_main() to initialize JNI bindings |
| void SDL_Android_Init(JNIEnv* mEnv, jclass cls) |
| { |
| __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()"); |
| |
| Android_JNI_SetupThread(); |
| |
| mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls)); |
| |
| midCreateGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "createGLContext","(II[I)Z"); |
| midDeleteGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "deleteGLContext","()V"); |
| midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "flipBuffers","()V"); |
| midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "audioInit", "(IZZI)I"); |
| midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "audioWriteShortBuffer", "([S)V"); |
| midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "audioWriteByteBuffer", "([B)V"); |
| midAudioQuit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "audioQuit", "()V"); |
| |
| bHasNewData = false; |
| |
| if(!midCreateGLContext || !midFlipBuffers || !midAudioInit || |
| !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) { |
| __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly"); |
| } |
| __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!"); |
| } |
| |
| // Resize |
| void Java_org_libsdl_app_SDLActivity_onNativeResize( |
| JNIEnv* env, jclass jcls, |
| jint width, jint height, jint format) |
| { |
| Android_SetScreenResolution(width, height, format); |
| } |
| |
| // Keydown |
| void Java_org_libsdl_app_SDLActivity_onNativeKeyDown( |
| JNIEnv* env, jclass jcls, jint keycode) |
| { |
| Android_OnKeyDown(keycode); |
| } |
| |
| // Keyup |
| void Java_org_libsdl_app_SDLActivity_onNativeKeyUp( |
| JNIEnv* env, jclass jcls, jint keycode) |
| { |
| Android_OnKeyUp(keycode); |
| } |
| |
| // Keyboard Focus Lost |
| void Java_org_libsdl_app_SDLActivity_onNativeKeyboardFocusLost( |
| JNIEnv* env, jclass jcls) |
| { |
| /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */ |
| SDL_StopTextInput(); |
| } |
| |
| |
| // Touch |
| void Java_org_libsdl_app_SDLActivity_onNativeTouch( |
| JNIEnv* env, jclass jcls, |
| jint touch_device_id_in, jint pointer_finger_id_in, |
| jint action, jfloat x, jfloat y, jfloat p) |
| { |
| Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p); |
| } |
| |
| // Accelerometer |
| void Java_org_libsdl_app_SDLActivity_onNativeAccel( |
| JNIEnv* env, jclass jcls, |
| jfloat x, jfloat y, jfloat z) |
| { |
| fLastAccelerometer[0] = x; |
| fLastAccelerometer[1] = y; |
| fLastAccelerometer[2] = z; |
| bHasNewData = true; |
| } |
| |
| // Low memory |
| void Java_org_libsdl_app_SDLActivity_nativeLowMemory( |
| JNIEnv* env, jclass cls) |
| { |
| SDL_SendAppEvent(SDL_APP_LOWMEMORY); |
| } |
| |
| // Quit |
| void Java_org_libsdl_app_SDLActivity_nativeQuit( |
| JNIEnv* env, jclass cls) |
| { |
| // Inject a SDL_QUIT event |
| SDL_SendQuit(); |
| SDL_SendAppEvent(SDL_APP_TERMINATING); |
| } |
| |
| // Pause |
| void Java_org_libsdl_app_SDLActivity_nativePause( |
| JNIEnv* env, jclass cls) |
| { |
| if (Android_Window) { |
| /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */ |
| if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem); |
| SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0); |
| SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0); |
| } |
| |
| __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()"); |
| SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND); |
| SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND); |
| } |
| |
| // Resume |
| void Java_org_libsdl_app_SDLActivity_nativeResume( |
| JNIEnv* env, jclass cls) |
| { |
| __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()"); |
| SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND); |
| SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND); |
| |
| if (Android_Window) { |
| /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context |
| * We can't restore the GL Context here because it needs to be done on the SDL main thread |
| * and this function will be called from the Java thread instead. |
| */ |
| if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem); |
| SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0); |
| SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0); |
| } |
| } |
| |
| void Java_org_libsdl_app_SDLInputConnection_nativeCommitText( |
| JNIEnv* env, jclass cls, |
| jstring text, jint newCursorPosition) |
| { |
| const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); |
| |
| SDL_SendKeyboardText(utftext); |
| |
| (*env)->ReleaseStringUTFChars(env, text, utftext); |
| } |
| |
| void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText( |
| JNIEnv* env, jclass cls, |
| jstring text, jint newCursorPosition) |
| { |
| const char *utftext = (*env)->GetStringUTFChars(env, text, NULL); |
| |
| SDL_SendEditingText(utftext, 0, 0); |
| |
| (*env)->ReleaseStringUTFChars(env, text, utftext); |
| } |
| |
| |
| |
| /******************************************************************************* |
| Functions called by SDL into Java |
| *******************************************************************************/ |
| |
| static int s_active = 0; |
| struct LocalReferenceHolder |
| { |
| JNIEnv *m_env; |
| const char *m_func; |
| }; |
| |
| static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func) |
| { |
| struct LocalReferenceHolder refholder; |
| refholder.m_env = NULL; |
| refholder.m_func = func; |
| #ifdef DEBUG_JNI |
| SDL_Log("Entering function %s", func); |
| #endif |
| return refholder; |
| } |
| |
| static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env) |
| { |
| const int capacity = 16; |
| if ((*env)->PushLocalFrame(env, capacity) < 0) { |
| SDL_SetError("Failed to allocate enough JVM local references"); |
| return SDL_FALSE; |
| } |
| ++s_active; |
| refholder->m_env = env; |
| return SDL_TRUE; |
| } |
| |
| static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder) |
| { |
| #ifdef DEBUG_JNI |
| SDL_Log("Leaving function %s", refholder->m_func); |
| #endif |
| if (refholder->m_env) { |
| JNIEnv* env = refholder->m_env; |
| (*env)->PopLocalFrame(env, NULL); |
| --s_active; |
| } |
| } |
| |
| static SDL_bool LocalReferenceHolder_IsActive() |
| { |
| return s_active > 0; |
| } |
| |
| |
| SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion, |
| int red, int green, int blue, int alpha, |
| int buffer, int depth, int stencil, |
| int buffers, int samples) |
| { |
| JNIEnv *env = Android_JNI_GetEnv(); |
| |
| jint attribs[] = { |
| EGL_RED_SIZE, red, |
| EGL_GREEN_SIZE, green, |
| EGL_BLUE_SIZE, blue, |
| EGL_ALPHA_SIZE, alpha, |
| EGL_BUFFER_SIZE, buffer, |
| EGL_DEPTH_SIZE, depth, |
| EGL_STENCIL_SIZE, stencil, |
| EGL_SAMPLE_BUFFERS, buffers, |
| EGL_SAMPLES, samples, |
| EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT), |
| EGL_NONE |
| }; |
| int len = SDL_arraysize(attribs); |
| |
| jintArray array; |
| |
| array = (*env)->NewIntArray(env, len); |
| (*env)->SetIntArrayRegion(env, array, 0, len, attribs); |
| |
| jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midCreateGLContext, majorVersion, minorVersion, array); |
| |
| (*env)->DeleteLocalRef(env, array); |
| |
| return success ? SDL_TRUE : SDL_FALSE; |
| } |
| |
| SDL_bool Android_JNI_DeleteContext(void) |
| { |
| /* There's only one context, so no parameter for now */ |
| JNIEnv *env = Android_JNI_GetEnv(); |
| (*env)->CallStaticVoidMethod(env, mActivityClass, midDeleteGLContext); |
| return SDL_TRUE; |
| } |
| |
| void Android_JNI_SwapWindow() |
| { |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midFlipBuffers); |
| } |
| |
| void Android_JNI_SetActivityTitle(const char *title) |
| { |
| jmethodID mid; |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z"); |
| if (mid) { |
| jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title)); |
| (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, mid, jtitle); |
| (*mEnv)->DeleteLocalRef(mEnv, jtitle); |
| } |
| } |
| |
| SDL_bool Android_JNI_GetAccelerometerValues(float values[3]) |
| { |
| int i; |
| SDL_bool retval = SDL_FALSE; |
| |
| if (bHasNewData) { |
| for (i = 0; i < 3; ++i) { |
| values[i] = fLastAccelerometer[i]; |
| } |
| bHasNewData = false; |
| retval = SDL_TRUE; |
| } |
| |
| return retval; |
| } |
| |
| static void Android_JNI_ThreadDestroyed(void* value) { |
| /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */ |
| JNIEnv *env = (JNIEnv*) value; |
| if (env != NULL) { |
| (*mJavaVM)->DetachCurrentThread(mJavaVM); |
| pthread_setspecific(mThreadKey, NULL); |
| } |
| } |
| |
| JNIEnv* Android_JNI_GetEnv(void) { |
| /* From http://developer.android.com/guide/practices/jni.html |
| * All threads are Linux threads, scheduled by the kernel. |
| * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then |
| * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the |
| * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv, |
| * and cannot make JNI calls. |
| * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main" |
| * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread |
| * is a no-op. |
| * Note: You can call this function any number of times for the same thread, there's no harm in it |
| */ |
| |
| JNIEnv *env; |
| int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL); |
| if(status < 0) { |
| LOGE("failed to attach current thread"); |
| return 0; |
| } |
| |
| return env; |
| } |
| |
| int Android_JNI_SetupThread(void) { |
| /* From http://developer.android.com/guide/practices/jni.html |
| * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward, |
| * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be |
| * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific |
| * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.) |
| * Note: The destructor is not called unless the stored value is != NULL |
| * Note: You can call this function any number of times for the same thread, there's no harm in it |
| * (except for some lost CPU cycles) |
| */ |
| JNIEnv *env = Android_JNI_GetEnv(); |
| pthread_setspecific(mThreadKey, (void*) env); |
| return 1; |
| } |
| |
| // |
| // Audio support |
| // |
| static jboolean audioBuffer16Bit = JNI_FALSE; |
| static jboolean audioBufferStereo = JNI_FALSE; |
| static jobject audioBuffer = NULL; |
| static void* audioBufferPinned = NULL; |
| |
| int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames) |
| { |
| int audioBufferFrames; |
| |
| JNIEnv *env = Android_JNI_GetEnv(); |
| |
| if (!env) { |
| LOGE("callback_handler: failed to attach current thread"); |
| } |
| Android_JNI_SetupThread(); |
| |
| __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device"); |
| audioBuffer16Bit = is16Bit; |
| audioBufferStereo = channelCount > 1; |
| |
| if ((*env)->CallStaticIntMethod(env, mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) { |
| /* Error during audio initialization */ |
| __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!"); |
| return 0; |
| } |
| |
| /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on |
| * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */ |
| |
| if (is16Bit) { |
| jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); |
| if (audioBufferLocal) { |
| audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal); |
| (*env)->DeleteLocalRef(env, audioBufferLocal); |
| } |
| } |
| else { |
| jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1)); |
| if (audioBufferLocal) { |
| audioBuffer = (*env)->NewGlobalRef(env, audioBufferLocal); |
| (*env)->DeleteLocalRef(env, audioBufferLocal); |
| } |
| } |
| |
| if (audioBuffer == NULL) { |
| __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!"); |
| return 0; |
| } |
| |
| jboolean isCopy = JNI_FALSE; |
| if (audioBuffer16Bit) { |
| audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy); |
| audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer); |
| } else { |
| audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy); |
| audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer); |
| } |
| if (audioBufferStereo) { |
| audioBufferFrames /= 2; |
| } |
| |
| return audioBufferFrames; |
| } |
| |
| void * Android_JNI_GetAudioBuffer() |
| { |
| return audioBufferPinned; |
| } |
| |
| void Android_JNI_WriteAudioBuffer() |
| { |
| JNIEnv *mAudioEnv = Android_JNI_GetEnv(); |
| |
| if (audioBuffer16Bit) { |
| (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT); |
| (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer); |
| } else { |
| (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT); |
| (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer); |
| } |
| |
| /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */ |
| } |
| |
| void Android_JNI_CloseAudioDevice() |
| { |
| JNIEnv *env = Android_JNI_GetEnv(); |
| |
| (*env)->CallStaticVoidMethod(env, mActivityClass, midAudioQuit); |
| |
| if (audioBuffer) { |
| (*env)->DeleteGlobalRef(env, audioBuffer); |
| audioBuffer = NULL; |
| audioBufferPinned = NULL; |
| } |
| } |
| |
| // Test for an exception and call SDL_SetError with its detail if one occurs |
| // If the parameter silent is truthy then SDL_SetError() will not be called. |
| static bool Android_JNI_ExceptionOccurred(bool silent) |
| { |
| SDL_assert(LocalReferenceHolder_IsActive()); |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| |
| jthrowable exception = (*mEnv)->ExceptionOccurred(mEnv); |
| if (exception != NULL) { |
| jmethodID mid; |
| |
| // Until this happens most JNI operations have undefined behaviour |
| (*mEnv)->ExceptionClear(mEnv); |
| |
| if (!silent) { |
| jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception); |
| jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class"); |
| |
| mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;"); |
| jstring exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid); |
| const char* exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0); |
| |
| mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;"); |
| jstring exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid); |
| |
| if (exceptionMessage != NULL) { |
| const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0); |
| SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8); |
| (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8); |
| } else { |
| SDL_SetError("%s", exceptionNameUTF8); |
| } |
| |
| (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8); |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| |
| int result = 0; |
| |
| jmethodID mid; |
| jobject context; |
| jobject assetManager; |
| jobject inputStream; |
| jclass channels; |
| jobject readableByteChannel; |
| jstring fileNameJString; |
| jobject fd; |
| jclass fdCls; |
| jfieldID descriptor; |
| |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, mEnv)) { |
| goto failure; |
| } |
| |
| fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef; |
| ctx->hidden.androidio.position = 0; |
| |
| // context = SDLActivity.getContext(); |
| mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, |
| "getContext","()Landroid/content/Context;"); |
| context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid); |
| |
| |
| // assetManager = context.getAssets(); |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), |
| "getAssets", "()Landroid/content/res/AssetManager;"); |
| assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid); |
| |
| /* First let's try opening the file to obtain an AssetFileDescriptor. |
| * This method reads the files directly from the APKs using standard *nix calls |
| */ |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;"); |
| inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString); |
| if (Android_JNI_ExceptionOccurred(true)) { |
| goto fallback; |
| } |
| |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J"); |
| ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); |
| if (Android_JNI_ExceptionOccurred(true)) { |
| goto fallback; |
| } |
| |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J"); |
| ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid); |
| if (Android_JNI_ExceptionOccurred(true)) { |
| goto fallback; |
| } |
| |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;"); |
| fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid); |
| fdCls = (*mEnv)->GetObjectClass(mEnv, fd); |
| descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I"); |
| ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor); |
| ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); |
| |
| // Seek to the correct offset in the file. |
| lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET); |
| |
| if (false) { |
| fallback: |
| // Disabled log message because of spam on the Nexus 7 |
| //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); |
| |
| /* Try the old method using InputStream */ |
| ctx->hidden.androidio.assetFileDescriptorRef = NULL; |
| |
| // inputStream = assetManager.open(<filename>); |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), |
| "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"); |
| inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/); |
| if (Android_JNI_ExceptionOccurred(false)) { |
| goto failure; |
| } |
| |
| ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream); |
| |
| // Despite all the visible documentation on [Asset]InputStream claiming |
| // that the .available() method is not guaranteed to return the entire file |
| // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ... |
| // android/apis/content/ReadAsset.java imply that Android's |
| // AssetInputStream.available() /will/ always return the total file size |
| |
| // size = inputStream.available(); |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), |
| "available", "()I"); |
| ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid); |
| if (Android_JNI_ExceptionOccurred(false)) { |
| goto failure; |
| } |
| |
| // readableByteChannel = Channels.newChannel(inputStream); |
| channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels"); |
| mid = (*mEnv)->GetStaticMethodID(mEnv, channels, |
| "newChannel", |
| "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;"); |
| readableByteChannel = (*mEnv)->CallStaticObjectMethod( |
| mEnv, channels, mid, inputStream); |
| if (Android_JNI_ExceptionOccurred(false)) { |
| goto failure; |
| } |
| |
| ctx->hidden.androidio.readableByteChannelRef = |
| (*mEnv)->NewGlobalRef(mEnv, readableByteChannel); |
| |
| // Store .read id for reading purposes |
| mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel), |
| "read", "(Ljava/nio/ByteBuffer;)I"); |
| ctx->hidden.androidio.readMethod = mid; |
| } |
| |
| if (false) { |
| failure: |
| result = -1; |
| |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); |
| |
| if(ctx->hidden.androidio.inputStreamRef != NULL) { |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); |
| } |
| |
| if(ctx->hidden.androidio.readableByteChannelRef != NULL) { |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); |
| } |
| |
| if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) { |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); |
| } |
| |
| } |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| return result; |
| } |
| |
| int Android_JNI_FileOpen(SDL_RWops* ctx, |
| const char* fileName, const char* mode) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| int retval; |
| |
| if (!LocalReferenceHolder_Init(&refs, mEnv)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| |
| if (!ctx) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| |
| jstring fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName); |
| ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString); |
| ctx->hidden.androidio.inputStreamRef = NULL; |
| ctx->hidden.androidio.readableByteChannelRef = NULL; |
| ctx->hidden.androidio.readMethod = NULL; |
| ctx->hidden.androidio.assetFileDescriptorRef = NULL; |
| |
| retval = Internal_Android_JNI_FileOpen(ctx); |
| LocalReferenceHolder_Cleanup(&refs); |
| return retval; |
| } |
| |
| size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer, |
| size_t size, size_t maxnum) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| |
| if (ctx->hidden.androidio.assetFileDescriptorRef) { |
| size_t bytesMax = size * maxnum; |
| if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) { |
| bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position; |
| } |
| size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax ); |
| if (result > 0) { |
| ctx->hidden.androidio.position += result; |
| LocalReferenceHolder_Cleanup(&refs); |
| return result / size; |
| } |
| LocalReferenceHolder_Cleanup(&refs); |
| return 0; |
| } else { |
| jlong bytesRemaining = (jlong) (size * maxnum); |
| jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position); |
| int bytesRead = 0; |
| |
| /* Don't read more bytes than those that remain in the file, otherwise we get an exception */ |
| if (bytesRemaining > bytesMax) bytesRemaining = bytesMax; |
| |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, mEnv)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return 0; |
| } |
| |
| jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef; |
| jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod; |
| jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining); |
| |
| while (bytesRemaining > 0) { |
| // result = readableByteChannel.read(...); |
| int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer); |
| |
| if (Android_JNI_ExceptionOccurred(false)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return 0; |
| } |
| |
| if (result < 0) { |
| break; |
| } |
| |
| bytesRemaining -= result; |
| bytesRead += result; |
| ctx->hidden.androidio.position += result; |
| } |
| LocalReferenceHolder_Cleanup(&refs); |
| return bytesRead / size; |
| } |
| } |
| |
| size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, |
| size_t size, size_t num) |
| { |
| SDL_SetError("Cannot write to Android package filesystem"); |
| return 0; |
| } |
| |
| static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, bool release) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| |
| int result = 0; |
| JNIEnv *mEnv = Android_JNI_GetEnv(); |
| |
| if (!LocalReferenceHolder_Init(&refs, mEnv)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return SDL_SetError("Failed to allocate enough JVM local references"); |
| } |
| |
| if (ctx) { |
| if (release) { |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef); |
| } |
| |
| if (ctx->hidden.androidio.assetFileDescriptorRef) { |
| jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef; |
| jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), |
| "close", "()V"); |
| (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef); |
| if (Android_JNI_ExceptionOccurred(false)) { |
| result = -1; |
| } |
| } |
| else { |
| jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef; |
| |
| // inputStream.close(); |
| jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), |
| "close", "()V"); |
| (*mEnv)->CallVoidMethod(mEnv, inputStream, mid); |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef); |
| (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef); |
| if (Android_JNI_ExceptionOccurred(false)) { |
| result = -1; |
| } |
| } |
| |
| if (release) { |
| SDL_FreeRW(ctx); |
| } |
| } |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| return result; |
| } |
| |
| |
| Sint64 Android_JNI_FileSize(SDL_RWops* ctx) |
| { |
| return ctx->hidden.androidio.size; |
| } |
| |
| Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence) |
| { |
| if (ctx->hidden.androidio.assetFileDescriptorRef) { |
| switch (whence) { |
| case RW_SEEK_SET: |
| if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; |
| offset += ctx->hidden.androidio.offset; |
| break; |
| case RW_SEEK_CUR: |
| offset += ctx->hidden.androidio.position; |
| if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size; |
| offset += ctx->hidden.androidio.offset; |
| break; |
| case RW_SEEK_END: |
| offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset; |
| break; |
| default: |
| return SDL_SetError("Unknown value for 'whence'"); |
| } |
| whence = SEEK_SET; |
| |
| off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET); |
| if (ret == -1) return -1; |
| ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset; |
| } else { |
| Sint64 newPosition; |
| |
| switch (whence) { |
| case RW_SEEK_SET: |
| newPosition = offset; |
| break; |
| case RW_SEEK_CUR: |
| newPosition = ctx->hidden.androidio.position + offset; |
| break; |
| case RW_SEEK_END: |
| newPosition = ctx->hidden.androidio.size + offset; |
| break; |
| default: |
| return SDL_SetError("Unknown value for 'whence'"); |
| } |
| |
| /* Validate the new position */ |
| if (newPosition < 0) { |
| return SDL_Error(SDL_EFSEEK); |
| } |
| if (newPosition > ctx->hidden.androidio.size) { |
| newPosition = ctx->hidden.androidio.size; |
| } |
| |
| Sint64 movement = newPosition - ctx->hidden.androidio.position; |
| if (movement > 0) { |
| unsigned char buffer[4096]; |
| |
| // The easy case where we're seeking forwards |
| while (movement > 0) { |
| Sint64 amount = sizeof (buffer); |
| if (amount > movement) { |
| amount = movement; |
| } |
| size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount); |
| if (result <= 0) { |
| // Failed to read/skip the required amount, so fail |
| return -1; |
| } |
| |
| movement -= result; |
| } |
| |
| } else if (movement < 0) { |
| // We can't seek backwards so we have to reopen the file and seek |
| // forwards which obviously isn't very efficient |
| Internal_Android_JNI_FileClose(ctx, false); |
| Internal_Android_JNI_FileOpen(ctx); |
| Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET); |
| } |
| } |
| |
| return ctx->hidden.androidio.position; |
| |
| } |
| |
| int Android_JNI_FileClose(SDL_RWops* ctx) |
| { |
| return Internal_Android_JNI_FileClose(ctx, true); |
| } |
| |
| // returns a new global reference which needs to be released later |
| static jobject Android_JNI_GetSystemServiceObject(const char* name) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| JNIEnv* env = Android_JNI_GetEnv(); |
| jobject retval = NULL; |
| |
| if (!LocalReferenceHolder_Init(&refs, env)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return NULL; |
| } |
| |
| jstring service = (*env)->NewStringUTF(env, name); |
| |
| jmethodID mid; |
| |
| mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); |
| jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); |
| |
| mid = (*env)->GetMethodID(env, mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); |
| jobject manager = (*env)->CallObjectMethod(env, context, mid, service); |
| |
| (*env)->DeleteLocalRef(env, service); |
| |
| retval = manager ? (*env)->NewGlobalRef(env, manager) : NULL; |
| LocalReferenceHolder_Cleanup(&refs); |
| return retval; |
| } |
| |
| #define SETUP_CLIPBOARD(error) \ |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); \ |
| JNIEnv* env = Android_JNI_GetEnv(); \ |
| if (!LocalReferenceHolder_Init(&refs, env)) { \ |
| LocalReferenceHolder_Cleanup(&refs); \ |
| return error; \ |
| } \ |
| jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \ |
| if (!clipboard) { \ |
| LocalReferenceHolder_Cleanup(&refs); \ |
| return error; \ |
| } |
| |
| #define CLEANUP_CLIPBOARD() \ |
| LocalReferenceHolder_Cleanup(&refs); |
| |
| int Android_JNI_SetClipboardText(const char* text) |
| { |
| SETUP_CLIPBOARD(-1) |
| |
| jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "setText", "(Ljava/lang/CharSequence;)V"); |
| jstring string = (*env)->NewStringUTF(env, text); |
| (*env)->CallVoidMethod(env, clipboard, mid, string); |
| (*env)->DeleteGlobalRef(env, clipboard); |
| (*env)->DeleteLocalRef(env, string); |
| |
| CLEANUP_CLIPBOARD(); |
| |
| return 0; |
| } |
| |
| char* Android_JNI_GetClipboardText() |
| { |
| SETUP_CLIPBOARD(SDL_strdup("")) |
| |
| jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "getText", "()Ljava/lang/CharSequence;"); |
| jobject sequence = (*env)->CallObjectMethod(env, clipboard, mid); |
| (*env)->DeleteGlobalRef(env, clipboard); |
| if (sequence) { |
| mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, sequence), "toString", "()Ljava/lang/String;"); |
| jstring string = (jstring)((*env)->CallObjectMethod(env, sequence, mid)); |
| const char* utf = (*env)->GetStringUTFChars(env, string, 0); |
| if (utf) { |
| char* text = SDL_strdup(utf); |
| (*env)->ReleaseStringUTFChars(env, string, utf); |
| |
| CLEANUP_CLIPBOARD(); |
| |
| return text; |
| } |
| } |
| |
| CLEANUP_CLIPBOARD(); |
| |
| return SDL_strdup(""); |
| } |
| |
| SDL_bool Android_JNI_HasClipboardText() |
| { |
| SETUP_CLIPBOARD(SDL_FALSE) |
| |
| jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, clipboard), "hasText", "()Z"); |
| jboolean has = (*env)->CallBooleanMethod(env, clipboard, mid); |
| (*env)->DeleteGlobalRef(env, clipboard); |
| |
| CLEANUP_CLIPBOARD(); |
| |
| return has ? SDL_TRUE : SDL_FALSE; |
| } |
| |
| |
| // returns 0 on success or -1 on error (others undefined then) |
| // returns truthy or falsy value in plugged, charged and battery |
| // returns the value in seconds and percent or -1 if not available |
| int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent) |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| JNIEnv* env = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, env)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| |
| jmethodID mid; |
| |
| mid = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;"); |
| jobject context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); |
| |
| jstring action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED"); |
| |
| jclass cls = (*env)->FindClass(env, "android/content/IntentFilter"); |
| |
| mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V"); |
| jobject filter = (*env)->NewObject(env, cls, mid, action); |
| |
| (*env)->DeleteLocalRef(env, action); |
| |
| mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;"); |
| jobject intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter); |
| |
| (*env)->DeleteLocalRef(env, filter); |
| |
| cls = (*env)->GetObjectClass(env, intent); |
| |
| jstring iname; |
| jmethodID imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I"); |
| |
| #define GET_INT_EXTRA(var, key) \ |
| iname = (*env)->NewStringUTF(env, key); \ |
| int var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \ |
| (*env)->DeleteLocalRef(env, iname); |
| |
| jstring bname; |
| jmethodID bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z"); |
| |
| #define GET_BOOL_EXTRA(var, key) \ |
| bname = (*env)->NewStringUTF(env, key); \ |
| int var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \ |
| (*env)->DeleteLocalRef(env, bname); |
| |
| if (plugged) { |
| GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5) |
| if (plug == -1) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| // 1 == BatteryManager.BATTERY_PLUGGED_AC |
| // 2 == BatteryManager.BATTERY_PLUGGED_USB |
| *plugged = (0 < plug) ? 1 : 0; |
| } |
| |
| if (charged) { |
| GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5) |
| if (status == -1) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| // 5 == BatteryManager.BATTERY_STATUS_FULL |
| *charged = (status == 5) ? 1 : 0; |
| } |
| |
| if (battery) { |
| GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5) |
| *battery = present ? 1 : 0; |
| } |
| |
| if (seconds) { |
| *seconds = -1; // not possible |
| } |
| |
| if (percent) { |
| GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5) |
| GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5) |
| if ((level == -1) || (scale == -1)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return -1; |
| } |
| *percent = level * 100 / scale; |
| } |
| |
| (*env)->DeleteLocalRef(env, intent); |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| return 0; |
| } |
| |
| // sends message to be handled on the UI event dispatch thread |
| int Android_JNI_SendMessage(int command, int param) |
| { |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!env) { |
| return -1; |
| } |
| jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z"); |
| if (!mid) { |
| return -1; |
| } |
| jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, command, param); |
| return success ? 0 : -1; |
| } |
| |
| void Android_JNI_ShowTextInput(SDL_Rect *inputRect) |
| { |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!env) { |
| return; |
| } |
| |
| jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIII)Z"); |
| if (!mid) { |
| return; |
| } |
| (*env)->CallStaticBooleanMethod(env, mActivityClass, mid, |
| inputRect->x, |
| inputRect->y, |
| inputRect->w, |
| inputRect->h ); |
| } |
| |
| void Android_JNI_HideTextInput() |
| { |
| // has to match Activity constant |
| const int COMMAND_TEXTEDIT_HIDE = 3; |
| Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // |
| // Functions exposed to SDL applications in SDL_system.h |
| // |
| |
| void *SDL_AndroidGetJNIEnv() |
| { |
| return Android_JNI_GetEnv(); |
| } |
| |
| |
| |
| void *SDL_AndroidGetActivity() |
| { |
| /* See SDL_system.h for caveats on using this function. */ |
| |
| jmethodID mid; |
| |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!env) { |
| return NULL; |
| } |
| |
| // return SDLActivity.getContext(); |
| mid = (*env)->GetStaticMethodID(env, mActivityClass, |
| "getContext","()Landroid/content/Context;"); |
| return (*env)->CallStaticObjectMethod(env, mActivityClass, mid); |
| } |
| |
| const char * SDL_AndroidGetInternalStoragePath() |
| { |
| static char *s_AndroidInternalFilesPath = NULL; |
| |
| if (!s_AndroidInternalFilesPath) { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| jmethodID mid; |
| jobject context; |
| jobject fileObject; |
| jstring pathString; |
| const char *path; |
| |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, env)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return NULL; |
| } |
| |
| // context = SDLActivity.getContext(); |
| mid = (*env)->GetStaticMethodID(env, mActivityClass, |
| "getContext","()Landroid/content/Context;"); |
| context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); |
| |
| // fileObj = context.getFilesDir(); |
| mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), |
| "getFilesDir", "()Ljava/io/File;"); |
| fileObject = (*env)->CallObjectMethod(env, context, mid); |
| if (!fileObject) { |
| SDL_SetError("Couldn't get internal directory"); |
| LocalReferenceHolder_Cleanup(&refs); |
| return NULL; |
| } |
| |
| // path = fileObject.getAbsolutePath(); |
| mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), |
| "getAbsolutePath", "()Ljava/lang/String;"); |
| pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); |
| |
| path = (*env)->GetStringUTFChars(env, pathString, NULL); |
| s_AndroidInternalFilesPath = SDL_strdup(path); |
| (*env)->ReleaseStringUTFChars(env, pathString, path); |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| } |
| return s_AndroidInternalFilesPath; |
| } |
| |
| int SDL_AndroidGetExternalStorageState() |
| { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| jmethodID mid; |
| jclass cls; |
| jstring stateString; |
| const char *state; |
| int stateFlags; |
| |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, env)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return 0; |
| } |
| |
| cls = (*env)->FindClass(env, "android/os/Environment"); |
| mid = (*env)->GetStaticMethodID(env, cls, |
| "getExternalStorageState", "()Ljava/lang/String;"); |
| stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid); |
| |
| state = (*env)->GetStringUTFChars(env, stateString, NULL); |
| |
| // Print an info message so people debugging know the storage state |
| __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state); |
| |
| if (SDL_strcmp(state, "mounted") == 0) { |
| stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ | |
| SDL_ANDROID_EXTERNAL_STORAGE_WRITE; |
| } else if (SDL_strcmp(state, "mounted_ro") == 0) { |
| stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ; |
| } else { |
| stateFlags = 0; |
| } |
| (*env)->ReleaseStringUTFChars(env, stateString, state); |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| return stateFlags; |
| } |
| |
| const char * SDL_AndroidGetExternalStoragePath() |
| { |
| static char *s_AndroidExternalFilesPath = NULL; |
| |
| if (!s_AndroidExternalFilesPath) { |
| struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); |
| jmethodID mid; |
| jobject context; |
| jobject fileObject; |
| jstring pathString; |
| const char *path; |
| |
| JNIEnv *env = Android_JNI_GetEnv(); |
| if (!LocalReferenceHolder_Init(&refs, env)) { |
| LocalReferenceHolder_Cleanup(&refs); |
| return NULL; |
| } |
| |
| // context = SDLActivity.getContext(); |
| mid = (*env)->GetStaticMethodID(env, mActivityClass, |
| "getContext","()Landroid/content/Context;"); |
| context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid); |
| |
| // fileObj = context.getExternalFilesDir(); |
| mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context), |
| "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;"); |
| fileObject = (*env)->CallObjectMethod(env, context, mid, NULL); |
| if (!fileObject) { |
| SDL_SetError("Couldn't get external directory"); |
| LocalReferenceHolder_Cleanup(&refs); |
| return NULL; |
| } |
| |
| // path = fileObject.getAbsolutePath(); |
| mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject), |
| "getAbsolutePath", "()Ljava/lang/String;"); |
| pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid); |
| |
| path = (*env)->GetStringUTFChars(env, pathString, NULL); |
| s_AndroidExternalFilesPath = SDL_strdup(path); |
| (*env)->ReleaseStringUTFChars(env, pathString, path); |
| |
| LocalReferenceHolder_Cleanup(&refs); |
| } |
| return s_AndroidExternalFilesPath; |
| } |
| |
| #endif /* __ANDROID__ */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |
| |