2 Simple DirectMedia Layer
3 Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
21 #include "../../SDL_internal.h"
22 #include "SDL_stdinc.h"
23 #include "SDL_assert.h"
24 #include "SDL_hints.h"
30 #include "SDL_system.h"
31 #include "SDL_android.h"
33 #include "keyinfotable.h"
35 #include "../../events/SDL_events_c.h"
36 #include "../../video/android/SDL_androidkeyboard.h"
37 #include "../../video/android/SDL_androidmouse.h"
38 #include "../../video/android/SDL_androidtouch.h"
39 #include "../../video/android/SDL_androidvideo.h"
40 #include "../../video/android/SDL_androidwindow.h"
41 #include "../../joystick/android/SDL_sysjoystick_c.h"
42 #include "../../haptic/android/SDL_syshaptic_c.h"
44 #include <android/log.h>
46 #include <sys/types.h>
49 /* #define LOG_TAG "SDL_android" */
50 /* #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */
51 /* #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */
52 #define LOGI(...) do {} while (0)
53 #define LOGE(...) do {} while (0)
56 #define SDL_JAVA_PREFIX org_libsdl_app
57 #define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function)
58 #define CONCAT2(prefix, class, function) Java_ ## prefix ## _ ## class ## _ ## function
59 #define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
60 #define SDL_JAVA_AUDIO_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function)
61 #define SDL_JAVA_CONTROLLER_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
62 #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
65 /* Java class SDLActivity */
66 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
67 JNIEnv* mEnv, jclass cls);
69 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
70 JNIEnv* env, jclass cls,
71 jstring library, jstring function, jobject array);
73 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
74 JNIEnv* env, jclass jcls,
77 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
78 JNIEnv* env, jclass jcls,
79 jint width, jint height, jint format, jfloat rate);
81 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
82 JNIEnv* env, jclass jcls);
84 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
85 JNIEnv* env, jclass jcls);
87 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
88 JNIEnv* env, jclass jcls,
91 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
92 JNIEnv* env, jclass jcls,
95 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
96 JNIEnv* env, jclass jcls);
98 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
99 JNIEnv* env, jclass jcls,
100 jint touch_device_id_in, jint pointer_finger_id_in,
101 jint action, jfloat x, jfloat y, jfloat p);
103 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
104 JNIEnv* env, jclass jcls,
105 jint button, jint action, jfloat x, jfloat y);
107 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
108 JNIEnv* env, jclass jcls,
109 jfloat x, jfloat y, jfloat z);
111 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
112 JNIEnv* env, jclass jcls);
114 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
115 JNIEnv* env, jclass cls);
117 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
118 JNIEnv* env, jclass cls);
120 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
121 JNIEnv* env, jclass cls);
123 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
124 JNIEnv* env, jclass cls);
126 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
127 JNIEnv* env, jclass cls,
130 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
131 JNIEnv* env, jclass cls,
132 jstring name, jstring value);
134 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeEnvironmentVariablesSet)(
135 JNIEnv* env, jclass cls);
137 /* Java class SDLInputConnection */
138 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
139 JNIEnv* env, jclass cls,
140 jstring text, jint newCursorPosition);
142 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
143 JNIEnv* env, jclass cls,
144 jstring text, jint newCursorPosition);
146 /* Java class SDLAudioManager */
147 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
148 JNIEnv *env, jclass jcls);
150 /* Java class SDLControllerManager */
151 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
152 JNIEnv *env, jclass jcls);
154 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
155 JNIEnv* env, jclass jcls,
156 jint device_id, jint keycode);
158 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
159 JNIEnv* env, jclass jcls,
160 jint device_id, jint keycode);
162 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
163 JNIEnv* env, jclass jcls,
164 jint device_id, jint axis, jfloat value);
166 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
167 JNIEnv* env, jclass jcls,
168 jint device_id, jint hat_id, jint x, jint y);
170 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
171 JNIEnv* env, jclass jcls,
172 jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
173 jint nbuttons, jint naxes, jint nhats, jint nballs);
175 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
176 JNIEnv* env, jclass jcls,
179 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
180 JNIEnv* env, jclass jcls,
181 jint device_id, jstring device_name);
183 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
184 JNIEnv* env, jclass jcls,
189 /* Uncomment this to log messages entering and exiting methods in this file */
190 /* #define DEBUG_JNI */
192 static void Android_JNI_ThreadDestroyed(void*);
194 /*******************************************************************************
195 This file links the Java side of Android with libsdl
196 *******************************************************************************/
200 /*******************************************************************************
202 *******************************************************************************/
203 static pthread_key_t mThreadKey;
204 static JavaVM* mJavaVM;
207 static jclass mActivityClass;
209 /* method signatures */
210 static jmethodID midGetNativeSurface;
211 static jmethodID midSetActivityTitle;
212 static jmethodID midSetWindowStyle;
213 static jmethodID midSetOrientation;
214 static jmethodID midGetContext;
215 static jmethodID midIsAndroidTV;
216 static jmethodID midInputGetInputDeviceIds;
217 static jmethodID midSendMessage;
218 static jmethodID midShowTextInput;
219 static jmethodID midIsScreenKeyboardShown;
220 static jmethodID midClipboardSetText;
221 static jmethodID midClipboardGetText;
222 static jmethodID midClipboardHasText;
223 static jmethodID midOpenAPKExpansionInputStream;
224 static jmethodID midGetManifestEnvironmentVariables;
225 static jmethodID midGetDisplayDPI;
228 static jclass mAudioManagerClass;
230 /* method signatures */
231 static jmethodID midAudioOpen;
232 static jmethodID midAudioWriteShortBuffer;
233 static jmethodID midAudioWriteByteBuffer;
234 static jmethodID midAudioClose;
235 static jmethodID midCaptureOpen;
236 static jmethodID midCaptureReadShortBuffer;
237 static jmethodID midCaptureReadByteBuffer;
238 static jmethodID midCaptureClose;
240 /* controller manager */
241 static jclass mControllerManagerClass;
243 /* method signatures */
244 static jmethodID midPollInputDevices;
245 static jmethodID midPollHapticDevices;
246 static jmethodID midHapticRun;
249 static jfieldID fidSeparateMouseAndTouch;
251 /* Accelerometer data storage */
252 static float fLastAccelerometer[3];
253 static SDL_bool bHasNewData;
255 static SDL_bool bHasEnvironmentVariables = SDL_FALSE;
257 /*******************************************************************************
258 Functions called by JNI
259 *******************************************************************************/
262 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
266 LOGI("JNI_OnLoad called");
267 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
268 LOGE("Failed to get the environment using GetEnv()");
272 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
273 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
275 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
276 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
278 Android_JNI_SetupThread();
280 return JNI_VERSION_1_4;
285 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
286 // We aren't fully initialized, let's just return.
293 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
294 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
296 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
298 Android_JNI_SetupThread();
300 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
302 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
303 "getNativeSurface","()Landroid/view/Surface;");
304 midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
305 "setActivityTitle","(Ljava/lang/String;)Z");
306 midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
307 "setWindowStyle","(Z)V");
308 midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
309 "setOrientation","(IIZLjava/lang/String;)V");
310 midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
311 "getContext","()Landroid/content/Context;");
312 midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
313 "isAndroidTV","()Z");
314 midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
315 "inputGetInputDeviceIds", "(I)[I");
316 midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
317 "sendMessage", "(II)Z");
318 midShowTextInput = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
319 "showTextInput", "(IIII)Z");
320 midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
321 "isScreenKeyboardShown","()Z");
322 midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
323 "clipboardSetText", "(Ljava/lang/String;)V");
324 midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
325 "clipboardGetText", "()Ljava/lang/String;");
326 midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
327 "clipboardHasText", "()Z");
328 midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
329 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
331 midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
332 "getManifestEnvironmentVariables", "()Z");
334 midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
335 midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
337 if (!midGetNativeSurface ||
338 !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsAndroidTV || !midInputGetInputDeviceIds ||
339 !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown ||
340 !midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
341 !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables|| !midGetDisplayDPI) {
342 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
345 fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
347 if (!fidSeparateMouseAndTouch) {
348 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
354 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
355 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
357 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
359 Android_JNI_SetupThread();
361 mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
363 midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
364 "audioOpen", "(IZZI)I");
365 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
366 "audioWriteShortBuffer", "([S)V");
367 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
368 "audioWriteByteBuffer", "([B)V");
369 midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
370 "audioClose", "()V");
371 midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
372 "captureOpen", "(IZZI)I");
373 midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
374 "captureReadShortBuffer", "([SZ)I");
375 midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
376 "captureReadByteBuffer", "([BZ)I");
377 midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
378 "captureClose", "()V");
380 if (!midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose ||
381 !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose) {
382 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
388 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
389 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
391 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
393 Android_JNI_SetupThread();
395 mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
397 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
398 "pollInputDevices", "()V");
399 midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
400 "pollHapticDevices", "()V");
401 midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
402 "hapticRun", "(II)V");
404 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun) {
405 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
411 /* SDL main function prototype */
412 typedef int (*SDL_main_func)(int argc, char *argv[]);
414 /* Start up the SDL app */
415 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
418 const char *library_file;
419 void *library_handle;
421 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
423 library_file = (*env)->GetStringUTFChars(env, library, NULL);
424 library_handle = dlopen(library_file, RTLD_GLOBAL);
425 if (library_handle) {
426 const char *function_name;
427 SDL_main_func SDL_main;
429 function_name = (*env)->GetStringUTFChars(env, function, NULL);
430 SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
437 /* Prepare the arguments. */
438 len = (*env)->GetArrayLength(env, array);
439 argv = SDL_stack_alloc(char*, 1 + len + 1);
441 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
442 https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
444 argv[argc++] = SDL_strdup("app_process");
445 for (i = 0; i < len; ++i) {
448 jstring string = (*env)->GetObjectArrayElement(env, array, i);
450 utf = (*env)->GetStringUTFChars(env, string, 0);
452 arg = SDL_strdup(utf);
453 (*env)->ReleaseStringUTFChars(env, string, utf);
455 (*env)->DeleteLocalRef(env, string);
458 arg = SDL_strdup("");
465 /* Run the application. */
466 status = SDL_main(argc, argv);
468 /* Release the arguments. */
469 for (i = 0; i < argc; ++i) {
472 SDL_stack_free(argv);
475 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
477 (*env)->ReleaseStringUTFChars(env, function, function_name);
479 dlclose(library_handle);
482 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
484 (*env)->ReleaseStringUTFChars(env, library, library_file);
486 /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
493 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
494 JNIEnv* env, jclass jcls,
497 const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
498 SDL_SendDropFile(NULL, path);
499 (*env)->ReleaseStringUTFChars(env, filename, path);
500 SDL_SendDropComplete(NULL);
504 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
505 JNIEnv* env, jclass jcls,
506 jint width, jint height, jint format, jfloat rate)
508 Android_SetScreenResolution(width, height, format, rate);
512 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
513 JNIEnv* env, jclass jcls,
514 jint device_id, jint keycode)
516 return Android_OnPadDown(device_id, keycode);
520 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
521 JNIEnv* env, jclass jcls,
522 jint device_id, jint keycode)
524 return Android_OnPadUp(device_id, keycode);
528 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
529 JNIEnv* env, jclass jcls,
530 jint device_id, jint axis, jfloat value)
532 Android_OnJoy(device_id, axis, value);
536 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
537 JNIEnv* env, jclass jcls,
538 jint device_id, jint hat_id, jint x, jint y)
540 Android_OnHat(device_id, hat_id, x, y);
544 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
545 JNIEnv* env, jclass jcls,
546 jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
547 jint nbuttons, jint naxes, jint nhats, jint nballs)
550 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
551 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
553 retval = Android_AddJoystick(device_id, name, desc, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs);
555 (*env)->ReleaseStringUTFChars(env, device_name, name);
556 (*env)->ReleaseStringUTFChars(env, device_desc, desc);
561 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
562 JNIEnv* env, jclass jcls,
565 return Android_RemoveJoystick(device_id);
568 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
569 JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
572 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
574 retval = Android_AddHaptic(device_id, name);
576 (*env)->ReleaseStringUTFChars(env, device_name, name);
581 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
582 JNIEnv* env, jclass jcls, jint device_id)
584 return Android_RemoveHaptic(device_id);
588 /* Surface Created */
589 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
591 SDL_WindowData *data;
592 SDL_VideoDevice *_this;
594 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
598 _this = SDL_GetVideoDevice();
599 data = (SDL_WindowData *) Android_Window->driverdata;
601 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
602 if (data->egl_surface == EGL_NO_SURFACE) {
603 if(data->native_window) {
604 ANativeWindow_release(data->native_window);
606 data->native_window = Android_JNI_GetNativeWindow();
607 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
610 /* GL Context handling is done in the event loop because this function is run from the Java thread */
614 /* Surface Destroyed */
615 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls)
617 /* We have to clear the current context and destroy the egl surface here
618 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
619 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
621 SDL_WindowData *data;
622 SDL_VideoDevice *_this;
624 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
628 _this = SDL_GetVideoDevice();
629 data = (SDL_WindowData *) Android_Window->driverdata;
631 if (data->egl_surface != EGL_NO_SURFACE) {
632 SDL_EGL_MakeCurrent(_this, NULL, NULL);
633 SDL_EGL_DestroySurface(_this, data->egl_surface);
634 data->egl_surface = EGL_NO_SURFACE;
637 /* GL Context handling is done in the event loop because this function is run from the Java thread */
642 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
643 JNIEnv* env, jclass jcls,
646 Android_OnKeyDown(keycode);
650 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
651 JNIEnv* env, jclass jcls,
654 Android_OnKeyUp(keycode);
657 /* Keyboard Focus Lost */
658 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
659 JNIEnv* env, jclass jcls)
661 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
667 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
668 JNIEnv* env, jclass jcls,
669 jint touch_device_id_in, jint pointer_finger_id_in,
670 jint action, jfloat x, jfloat y, jfloat p)
672 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
676 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
677 JNIEnv* env, jclass jcls,
678 jint button, jint action, jfloat x, jfloat y)
680 Android_OnMouse(button, action, x, y);
684 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
685 JNIEnv* env, jclass jcls,
686 jfloat x, jfloat y, jfloat z)
688 fLastAccelerometer[0] = x;
689 fLastAccelerometer[1] = y;
690 fLastAccelerometer[2] = z;
691 bHasNewData = SDL_TRUE;
695 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
696 JNIEnv* env, jclass jcls)
698 SDL_SendClipboardUpdate();
702 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
703 JNIEnv* env, jclass cls)
705 SDL_SendAppEvent(SDL_APP_LOWMEMORY);
709 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
710 JNIEnv* env, jclass cls)
712 /* Discard previous events. The user should have handled state storage
713 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
714 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
715 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
716 /* Inject a SDL_QUIT event */
718 SDL_SendAppEvent(SDL_APP_TERMINATING);
719 /* Resume the event loop so that the app can catch SDL_QUIT which
720 * should now be the top event in the event queue. */
721 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
725 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
726 JNIEnv* env, jclass cls)
728 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
730 if (Android_Window) {
731 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
732 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
733 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
734 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
736 /* *After* sending the relevant events, signal the pause semaphore
737 * so the event loop knows to pause and (optionally) block itself */
738 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
743 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
744 JNIEnv* env, jclass cls)
746 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
748 if (Android_Window) {
749 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
750 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
751 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
752 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
753 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
754 * We can't restore the GL Context here because it needs to be done on the SDL main thread
755 * and this function will be called from the Java thread instead.
757 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
761 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
762 JNIEnv* env, jclass cls,
763 jstring text, jint newCursorPosition)
765 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
767 SDL_SendKeyboardText(utftext);
769 (*env)->ReleaseStringUTFChars(env, text, utftext);
772 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
773 JNIEnv* env, jclass cls,
776 SDL_Scancode code = SDL_SCANCODE_UNKNOWN;
779 // We do not care about bigger than 127.
780 if (chUnicode < 127) {
781 AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode];
786 if (mod & KMOD_SHIFT) {
787 /* If character uses shift, press shift down */
788 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
791 /* send a keydown and keyup even for the character */
792 SDL_SendKeyboardKey(SDL_PRESSED, code);
793 SDL_SendKeyboardKey(SDL_RELEASED, code);
795 if (mod & KMOD_SHIFT) {
796 /* If character uses shift, press shift back up */
797 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
802 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
803 JNIEnv* env, jclass cls,
804 jstring text, jint newCursorPosition)
806 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
808 SDL_SendEditingText(utftext, 0, 0);
810 (*env)->ReleaseStringUTFChars(env, text, utftext);
813 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
814 JNIEnv* env, jclass cls,
817 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
818 const char *hint = SDL_GetHint(utfname);
820 jstring result = (*env)->NewStringUTF(env, hint);
821 (*env)->ReleaseStringUTFChars(env, name, utfname);
826 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
827 JNIEnv* env, jclass cls,
828 jstring name, jstring value)
830 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
831 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
833 SDL_setenv(utfname, utfvalue, 1);
835 (*env)->ReleaseStringUTFChars(env, name, utfname);
836 (*env)->ReleaseStringUTFChars(env, value, utfvalue);
840 /*******************************************************************************
841 Functions called by SDL into Java
842 *******************************************************************************/
844 static int s_active = 0;
845 struct LocalReferenceHolder
851 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
853 struct LocalReferenceHolder refholder;
854 refholder.m_env = NULL;
855 refholder.m_func = func;
857 SDL_Log("Entering function %s", func);
862 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
864 const int capacity = 16;
865 if ((*env)->PushLocalFrame(env, capacity) < 0) {
866 SDL_SetError("Failed to allocate enough JVM local references");
870 refholder->m_env = env;
874 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
877 SDL_Log("Leaving function %s", refholder->m_func);
879 if (refholder->m_env) {
880 JNIEnv* env = refholder->m_env;
881 (*env)->PopLocalFrame(env, NULL);
886 static SDL_bool LocalReferenceHolder_IsActive(void)
891 ANativeWindow* Android_JNI_GetNativeWindow(void)
895 JNIEnv *env = Android_JNI_GetEnv();
897 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
898 anw = ANativeWindow_fromSurface(env, s);
899 (*env)->DeleteLocalRef(env, s);
904 void Android_JNI_SetActivityTitle(const char *title)
906 JNIEnv *mEnv = Android_JNI_GetEnv();
908 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
909 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle);
910 (*mEnv)->DeleteLocalRef(mEnv, jtitle);
913 void Android_JNI_SetWindowStyle(SDL_bool fullscreen)
915 JNIEnv *mEnv = Android_JNI_GetEnv();
916 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
919 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
921 JNIEnv *mEnv = Android_JNI_GetEnv();
923 jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
924 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
925 (*mEnv)->DeleteLocalRef(mEnv, jhint);
928 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
931 SDL_bool retval = SDL_FALSE;
934 for (i = 0; i < 3; ++i) {
935 values[i] = fLastAccelerometer[i];
937 bHasNewData = SDL_FALSE;
944 static void Android_JNI_ThreadDestroyed(void* value)
946 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
947 JNIEnv *env = (JNIEnv*) value;
949 (*mJavaVM)->DetachCurrentThread(mJavaVM);
950 pthread_setspecific(mThreadKey, NULL);
954 JNIEnv* Android_JNI_GetEnv(void)
956 /* From http://developer.android.com/guide/practices/jni.html
957 * All threads are Linux threads, scheduled by the kernel.
958 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
959 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
960 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
961 * and cannot make JNI calls.
962 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
963 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
965 * Note: You can call this function any number of times for the same thread, there's no harm in it
969 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
971 LOGE("failed to attach current thread");
975 /* From http://developer.android.com/guide/practices/jni.html
976 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
977 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
978 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
979 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
980 * Note: The destructor is not called unless the stored value is != NULL
981 * Note: You can call this function any number of times for the same thread, there's no harm in it
982 * (except for some lost CPU cycles)
984 pthread_setspecific(mThreadKey, (void*) env);
989 int Android_JNI_SetupThread(void)
991 Android_JNI_GetEnv();
998 static jboolean audioBuffer16Bit = JNI_FALSE;
999 static jobject audioBuffer = NULL;
1000 static void* audioBufferPinned = NULL;
1001 static jboolean captureBuffer16Bit = JNI_FALSE;
1002 static jobject captureBuffer = NULL;
1004 int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
1006 jboolean audioBufferStereo;
1007 int audioBufferFrames;
1008 jobject jbufobj = NULL;
1011 JNIEnv *env = Android_JNI_GetEnv();
1014 LOGE("callback_handler: failed to attach current thread");
1016 Android_JNI_SetupThread();
1018 audioBufferStereo = channelCount > 1;
1021 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
1022 captureBuffer16Bit = is16Bit;
1023 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1024 /* Error during audio initialization */
1025 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
1029 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
1030 audioBuffer16Bit = is16Bit;
1031 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1032 /* Error during audio initialization */
1033 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
1038 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
1039 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
1042 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1043 if (audioBufferLocal) {
1044 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1045 (*env)->DeleteLocalRef(env, audioBufferLocal);
1049 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1050 if (audioBufferLocal) {
1051 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1052 (*env)->DeleteLocalRef(env, audioBufferLocal);
1056 if (jbufobj == NULL) {
1057 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
1062 captureBuffer = jbufobj;
1064 audioBuffer = jbufobj;
1071 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
1073 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
1076 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
1078 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
1081 if (audioBufferStereo) {
1082 audioBufferFrames /= 2;
1085 return audioBufferFrames;
1088 int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi)
1090 JNIEnv *env = Android_JNI_GetEnv();
1092 jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI);
1093 jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj);
1095 jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F");
1096 jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F");
1097 jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I");
1099 float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi);
1100 float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi);
1101 int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi);
1104 (*env)->DeleteLocalRef(env, jDisplayObj);
1105 (*env)->DeleteLocalRef(env, jDisplayClass);
1108 *ddpi = (float)nativeDdpi;
1120 void * Android_JNI_GetAudioBuffer(void)
1122 return audioBufferPinned;
1125 void Android_JNI_WriteAudioBuffer(void)
1127 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
1129 if (audioBuffer16Bit) {
1130 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
1131 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
1133 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
1134 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
1137 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
1140 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
1142 JNIEnv *env = Android_JNI_GetEnv();
1143 jboolean isCopy = JNI_FALSE;
1146 if (captureBuffer16Bit) {
1147 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
1148 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
1150 jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
1152 SDL_memcpy(buffer, ptr, br);
1153 (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
1156 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
1157 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
1159 jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
1160 SDL_memcpy(buffer, ptr, br);
1161 (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
1168 void Android_JNI_FlushCapturedAudio(void)
1170 JNIEnv *env = Android_JNI_GetEnv();
1171 #if 0 /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
1172 if (captureBuffer16Bit) {
1173 const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
1174 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1176 const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
1177 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1180 if (captureBuffer16Bit) {
1181 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
1183 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
1188 void Android_JNI_CloseAudioDevice(const int iscapture)
1190 JNIEnv *env = Android_JNI_GetEnv();
1193 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
1194 if (captureBuffer) {
1195 (*env)->DeleteGlobalRef(env, captureBuffer);
1196 captureBuffer = NULL;
1199 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
1201 (*env)->DeleteGlobalRef(env, audioBuffer);
1203 audioBufferPinned = NULL;
1208 /* Test for an exception and call SDL_SetError with its detail if one occurs */
1209 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
1210 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
1212 JNIEnv *mEnv = Android_JNI_GetEnv();
1213 jthrowable exception;
1215 SDL_assert(LocalReferenceHolder_IsActive());
1217 exception = (*mEnv)->ExceptionOccurred(mEnv);
1218 if (exception != NULL) {
1221 /* Until this happens most JNI operations have undefined behaviour */
1222 (*mEnv)->ExceptionClear(mEnv);
1225 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
1226 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
1227 jstring exceptionName;
1228 const char* exceptionNameUTF8;
1229 jstring exceptionMessage;
1231 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
1232 exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
1233 exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
1235 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
1236 exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
1238 if (exceptionMessage != NULL) {
1239 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
1240 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
1241 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
1243 SDL_SetError("%s", exceptionNameUTF8);
1246 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
1255 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
1257 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1263 jobject assetManager;
1264 jobject inputStream;
1266 jobject readableByteChannel;
1267 jstring fileNameJString;
1270 jfieldID descriptor;
1272 JNIEnv *mEnv = Android_JNI_GetEnv();
1273 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1277 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
1278 ctx->hidden.androidio.position = 0;
1280 /* context = SDLActivity.getContext(); */
1281 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext);
1283 /* assetManager = context.getAssets(); */
1284 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
1285 "getAssets", "()Landroid/content/res/AssetManager;");
1286 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
1288 /* First let's try opening the file to obtain an AssetFileDescriptor.
1289 * This method reads the files directly from the APKs using standard *nix calls
1291 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
1292 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
1293 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1297 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
1298 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1299 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1303 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
1304 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1305 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1309 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
1310 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
1311 fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
1312 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
1313 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
1314 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1316 /* Seek to the correct offset in the file. */
1317 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
1321 /* Disabled log message because of spam on the Nexus 7 */
1322 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
1324 /* Try the old method using InputStream */
1325 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1327 /* inputStream = assetManager.open(<filename>); */
1328 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
1329 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
1330 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
1331 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1332 /* Try fallback to APK expansion files */
1333 inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
1335 /* Exception is checked first because it always needs to be cleared.
1336 * If no exception occurred then the last SDL error message is kept.
1338 if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
1343 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1345 /* Despite all the visible documentation on [Asset]InputStream claiming
1346 * that the .available() method is not guaranteed to return the entire file
1347 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
1348 * android/apis/content/ReadAsset.java imply that Android's
1349 * AssetInputStream.available() /will/ always return the total file size
1352 /* size = inputStream.available(); */
1353 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1354 "available", "()I");
1355 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
1356 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1360 /* readableByteChannel = Channels.newChannel(inputStream); */
1361 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
1362 mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
1364 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
1365 readableByteChannel = (*mEnv)->CallStaticObjectMethod(
1366 mEnv, channels, mid, inputStream);
1367 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1371 ctx->hidden.androidio.readableByteChannelRef =
1372 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
1374 /* Store .read id for reading purposes */
1375 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
1376 "read", "(Ljava/nio/ByteBuffer;)I");
1377 ctx->hidden.androidio.readMethod = mid;
1384 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1386 if(ctx->hidden.androidio.inputStreamRef != NULL) {
1387 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1390 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
1391 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1394 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
1395 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1400 LocalReferenceHolder_Cleanup(&refs);
1404 int Android_JNI_FileOpen(SDL_RWops* ctx,
1405 const char* fileName, const char* mode)
1407 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1408 JNIEnv *mEnv = Android_JNI_GetEnv();
1410 jstring fileNameJString;
1412 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1413 LocalReferenceHolder_Cleanup(&refs);
1418 LocalReferenceHolder_Cleanup(&refs);
1422 fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
1423 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
1424 ctx->hidden.androidio.inputStreamRef = NULL;
1425 ctx->hidden.androidio.readableByteChannelRef = NULL;
1426 ctx->hidden.androidio.readMethod = NULL;
1427 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1429 retval = Internal_Android_JNI_FileOpen(ctx);
1430 LocalReferenceHolder_Cleanup(&refs);
1434 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
1435 size_t size, size_t maxnum)
1437 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1439 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1440 size_t bytesMax = size * maxnum;
1442 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
1443 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
1445 result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
1447 ctx->hidden.androidio.position += result;
1448 LocalReferenceHolder_Cleanup(&refs);
1449 return result / size;
1451 LocalReferenceHolder_Cleanup(&refs);
1454 jlong bytesRemaining = (jlong) (size * maxnum);
1455 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
1458 jobject readableByteChannel;
1459 jmethodID readMethod;
1462 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
1463 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
1465 mEnv = Android_JNI_GetEnv();
1466 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1467 LocalReferenceHolder_Cleanup(&refs);
1471 readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
1472 readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
1473 byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
1475 while (bytesRemaining > 0) {
1476 /* result = readableByteChannel.read(...); */
1477 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
1479 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1480 LocalReferenceHolder_Cleanup(&refs);
1488 bytesRemaining -= result;
1489 bytesRead += result;
1490 ctx->hidden.androidio.position += result;
1492 LocalReferenceHolder_Cleanup(&refs);
1493 return bytesRead / size;
1497 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
1498 size_t size, size_t num)
1500 SDL_SetError("Cannot write to Android package filesystem");
1504 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
1506 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1509 JNIEnv *mEnv = Android_JNI_GetEnv();
1511 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1512 LocalReferenceHolder_Cleanup(&refs);
1513 return SDL_SetError("Failed to allocate enough JVM local references");
1518 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1521 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1522 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
1523 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1525 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1526 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1527 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1532 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
1534 /* inputStream.close(); */
1535 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1537 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1538 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1539 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1540 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1550 LocalReferenceHolder_Cleanup(&refs);
1555 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
1557 return ctx->hidden.androidio.size;
1560 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
1562 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1566 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1567 offset += ctx->hidden.androidio.offset;
1570 offset += ctx->hidden.androidio.position;
1571 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1572 offset += ctx->hidden.androidio.offset;
1575 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
1578 return SDL_SetError("Unknown value for 'whence'");
1582 ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
1583 if (ret == -1) return -1;
1584 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
1591 newPosition = offset;
1594 newPosition = ctx->hidden.androidio.position + offset;
1597 newPosition = ctx->hidden.androidio.size + offset;
1600 return SDL_SetError("Unknown value for 'whence'");
1603 /* Validate the new position */
1604 if (newPosition < 0) {
1605 return SDL_Error(SDL_EFSEEK);
1607 if (newPosition > ctx->hidden.androidio.size) {
1608 newPosition = ctx->hidden.androidio.size;
1611 movement = newPosition - ctx->hidden.androidio.position;
1613 unsigned char buffer[4096];
1615 /* The easy case where we're seeking forwards */
1616 while (movement > 0) {
1617 Sint64 amount = sizeof (buffer);
1619 if (amount > movement) {
1622 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
1624 /* Failed to read/skip the required amount, so fail */
1631 } else if (movement < 0) {
1632 /* We can't seek backwards so we have to reopen the file and seek */
1633 /* forwards which obviously isn't very efficient */
1634 Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
1635 Internal_Android_JNI_FileOpen(ctx);
1636 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1640 return ctx->hidden.androidio.position;
1644 int Android_JNI_FileClose(SDL_RWops* ctx)
1646 return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
1649 int Android_JNI_SetClipboardText(const char* text)
1651 JNIEnv* env = Android_JNI_GetEnv();
1652 jstring string = (*env)->NewStringUTF(env, text);
1653 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
1654 (*env)->DeleteLocalRef(env, string);
1658 char* Android_JNI_GetClipboardText(void)
1660 JNIEnv* env = Android_JNI_GetEnv();
1664 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
1666 const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1668 text = SDL_strdup(utf);
1669 (*env)->ReleaseStringUTFChars(env, string, utf);
1671 (*env)->DeleteLocalRef(env, string);
1674 return (text == NULL) ? SDL_strdup("") : text;
1677 SDL_bool Android_JNI_HasClipboardText(void)
1679 JNIEnv* env = Android_JNI_GetEnv();
1680 jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
1681 return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
1684 /* returns 0 on success or -1 on error (others undefined then)
1685 * returns truthy or falsy value in plugged, charged and battery
1686 * returns the value in seconds and percent or -1 if not available
1688 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1690 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1691 JNIEnv* env = Android_JNI_GetEnv();
1702 if (!LocalReferenceHolder_Init(&refs, env)) {
1703 LocalReferenceHolder_Cleanup(&refs);
1708 /* context = SDLActivity.getContext(); */
1709 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1711 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1713 cls = (*env)->FindClass(env, "android/content/IntentFilter");
1715 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1716 filter = (*env)->NewObject(env, cls, mid, action);
1718 (*env)->DeleteLocalRef(env, action);
1720 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1721 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1723 (*env)->DeleteLocalRef(env, filter);
1725 cls = (*env)->GetObjectClass(env, intent);
1727 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1729 /* Watch out for C89 scoping rules because of the macro */
1730 #define GET_INT_EXTRA(var, key) \
1732 iname = (*env)->NewStringUTF(env, key); \
1733 var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1734 (*env)->DeleteLocalRef(env, iname);
1736 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1738 /* Watch out for C89 scoping rules because of the macro */
1739 #define GET_BOOL_EXTRA(var, key) \
1741 bname = (*env)->NewStringUTF(env, key); \
1742 var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1743 (*env)->DeleteLocalRef(env, bname);
1746 /* Watch out for C89 scoping rules because of the macro */
1747 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1749 LocalReferenceHolder_Cleanup(&refs);
1752 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1753 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1754 *plugged = (0 < plug) ? 1 : 0;
1758 /* Watch out for C89 scoping rules because of the macro */
1759 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1761 LocalReferenceHolder_Cleanup(&refs);
1764 /* 5 == BatteryManager.BATTERY_STATUS_FULL */
1765 *charged = (status == 5) ? 1 : 0;
1769 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1770 *battery = present ? 1 : 0;
1774 *seconds = -1; /* not possible */
1781 /* Watch out for C89 scoping rules because of the macro */
1783 GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1786 /* Watch out for C89 scoping rules because of the macro */
1788 GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1792 if ((level == -1) || (scale == -1)) {
1793 LocalReferenceHolder_Cleanup(&refs);
1796 *percent = level * 100 / scale;
1799 (*env)->DeleteLocalRef(env, intent);
1801 LocalReferenceHolder_Cleanup(&refs);
1805 /* returns number of found touch devices as return value and ids in parameter ids */
1806 int Android_JNI_GetTouchDeviceIds(int **ids) {
1807 JNIEnv *env = Android_JNI_GetEnv();
1808 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1809 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources);
1813 number = (int) (*env)->GetArrayLength(env, array);
1815 jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1818 *ids = SDL_malloc(number * sizeof (**ids));
1819 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1820 (*ids)[i] = elements[i];
1822 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1825 (*env)->DeleteLocalRef(env, array);
1830 /* sets the mSeparateMouseAndTouch field */
1831 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
1833 JNIEnv *env = Android_JNI_GetEnv();
1834 (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
1837 void Android_JNI_PollInputDevices(void)
1839 JNIEnv *env = Android_JNI_GetEnv();
1840 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
1843 void Android_JNI_PollHapticDevices(void)
1845 JNIEnv *env = Android_JNI_GetEnv();
1846 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
1849 void Android_JNI_HapticRun(int device_id, int length)
1851 JNIEnv *env = Android_JNI_GetEnv();
1852 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, length);
1856 /* See SDLActivity.java for constants. */
1857 #define COMMAND_SET_KEEP_SCREEN_ON 5
1859 /* sends message to be handled on the UI event dispatch thread */
1860 int Android_JNI_SendMessage(int command, int param)
1862 JNIEnv *env = Android_JNI_GetEnv();
1864 success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
1865 return success ? 0 : -1;
1868 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
1870 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
1873 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1875 JNIEnv *env = Android_JNI_GetEnv();
1876 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
1883 void Android_JNI_HideTextInput(void)
1885 /* has to match Activity constant */
1886 const int COMMAND_TEXTEDIT_HIDE = 3;
1887 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1890 SDL_bool Android_JNI_IsScreenKeyboardShown()
1892 JNIEnv *mEnv = Android_JNI_GetEnv();
1893 jboolean is_shown = 0;
1894 is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown);
1899 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
1907 jintArray button_flags;
1908 jintArray button_ids;
1909 jobjectArray button_texts;
1915 env = Android_JNI_GetEnv();
1917 /* convert parameters */
1919 clazz = (*env)->FindClass(env, "java/lang/String");
1921 title = (*env)->NewStringUTF(env, messageboxdata->title);
1922 message = (*env)->NewStringUTF(env, messageboxdata->message);
1924 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1925 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1926 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
1928 for (i = 0; i < messageboxdata->numbuttons; ++i) {
1929 temp = messageboxdata->buttons[i].flags;
1930 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
1931 temp = messageboxdata->buttons[i].buttonid;
1932 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
1933 text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
1934 (*env)->SetObjectArrayElement(env, button_texts, i, text);
1935 (*env)->DeleteLocalRef(env, text);
1938 if (messageboxdata->colorScheme) {
1939 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
1940 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
1941 temp = (0xFF << 24) |
1942 (messageboxdata->colorScheme->colors[i].r << 16) |
1943 (messageboxdata->colorScheme->colors[i].g << 8) |
1944 (messageboxdata->colorScheme->colors[i].b << 0);
1945 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
1951 (*env)->DeleteLocalRef(env, clazz);
1953 /* context = SDLActivity.getContext(); */
1954 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1956 clazz = (*env)->GetObjectClass(env, context);
1958 mid = (*env)->GetMethodID(env, clazz,
1959 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
1960 *buttonid = (*env)->CallIntMethod(env, context, mid,
1961 messageboxdata->flags,
1969 (*env)->DeleteLocalRef(env, context);
1970 (*env)->DeleteLocalRef(env, clazz);
1972 /* delete parameters */
1974 (*env)->DeleteLocalRef(env, title);
1975 (*env)->DeleteLocalRef(env, message);
1976 (*env)->DeleteLocalRef(env, button_flags);
1977 (*env)->DeleteLocalRef(env, button_ids);
1978 (*env)->DeleteLocalRef(env, button_texts);
1979 (*env)->DeleteLocalRef(env, colors);
1985 //////////////////////////////////////////////////////////////////////////////
1987 // Functions exposed to SDL applications in SDL_system.h
1988 //////////////////////////////////////////////////////////////////////////////
1991 void *SDL_AndroidGetJNIEnv(void)
1993 return Android_JNI_GetEnv();
1996 void *SDL_AndroidGetActivity(void)
1998 /* See SDL_system.h for caveats on using this function. */
2000 JNIEnv *env = Android_JNI_GetEnv();
2005 /* return SDLActivity.getContext(); */
2006 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2009 SDL_bool SDL_IsAndroidTV(void)
2011 JNIEnv *env = Android_JNI_GetEnv();
2012 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
2015 const char * SDL_AndroidGetInternalStoragePath(void)
2017 static char *s_AndroidInternalFilesPath = NULL;
2019 if (!s_AndroidInternalFilesPath) {
2020 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2027 JNIEnv *env = Android_JNI_GetEnv();
2028 if (!LocalReferenceHolder_Init(&refs, env)) {
2029 LocalReferenceHolder_Cleanup(&refs);
2033 /* context = SDLActivity.getContext(); */
2034 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2036 SDL_SetError("Couldn't get Android context!");
2037 LocalReferenceHolder_Cleanup(&refs);
2041 /* fileObj = context.getFilesDir(); */
2042 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2043 "getFilesDir", "()Ljava/io/File;");
2044 fileObject = (*env)->CallObjectMethod(env, context, mid);
2046 SDL_SetError("Couldn't get internal directory");
2047 LocalReferenceHolder_Cleanup(&refs);
2051 /* path = fileObject.getCanonicalPath(); */
2052 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2053 "getCanonicalPath", "()Ljava/lang/String;");
2054 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2055 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
2056 LocalReferenceHolder_Cleanup(&refs);
2060 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2061 s_AndroidInternalFilesPath = SDL_strdup(path);
2062 (*env)->ReleaseStringUTFChars(env, pathString, path);
2064 LocalReferenceHolder_Cleanup(&refs);
2066 return s_AndroidInternalFilesPath;
2069 int SDL_AndroidGetExternalStorageState(void)
2071 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2074 jstring stateString;
2078 JNIEnv *env = Android_JNI_GetEnv();
2079 if (!LocalReferenceHolder_Init(&refs, env)) {
2080 LocalReferenceHolder_Cleanup(&refs);
2084 cls = (*env)->FindClass(env, "android/os/Environment");
2085 mid = (*env)->GetStaticMethodID(env, cls,
2086 "getExternalStorageState", "()Ljava/lang/String;");
2087 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
2089 state = (*env)->GetStringUTFChars(env, stateString, NULL);
2091 /* Print an info message so people debugging know the storage state */
2092 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
2094 if (SDL_strcmp(state, "mounted") == 0) {
2095 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
2096 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
2097 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
2098 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
2102 (*env)->ReleaseStringUTFChars(env, stateString, state);
2104 LocalReferenceHolder_Cleanup(&refs);
2108 const char * SDL_AndroidGetExternalStoragePath(void)
2110 static char *s_AndroidExternalFilesPath = NULL;
2112 if (!s_AndroidExternalFilesPath) {
2113 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2120 JNIEnv *env = Android_JNI_GetEnv();
2121 if (!LocalReferenceHolder_Init(&refs, env)) {
2122 LocalReferenceHolder_Cleanup(&refs);
2126 /* context = SDLActivity.getContext(); */
2127 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2129 /* fileObj = context.getExternalFilesDir(); */
2130 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2131 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
2132 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2134 SDL_SetError("Couldn't get external directory");
2135 LocalReferenceHolder_Cleanup(&refs);
2139 /* path = fileObject.getAbsolutePath(); */
2140 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2141 "getAbsolutePath", "()Ljava/lang/String;");
2142 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2144 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2145 s_AndroidExternalFilesPath = SDL_strdup(path);
2146 (*env)->ReleaseStringUTFChars(env, pathString, path);
2148 LocalReferenceHolder_Cleanup(&refs);
2150 return s_AndroidExternalFilesPath;
2153 void Android_JNI_GetManifestEnvironmentVariables(void)
2155 if (!mActivityClass || !midGetManifestEnvironmentVariables) {
2156 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
2160 if (!bHasEnvironmentVariables) {
2161 JNIEnv *env = Android_JNI_GetEnv();
2162 SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
2164 bHasEnvironmentVariables = SDL_TRUE;
2169 #endif /* __ANDROID__ */
2171 /* vi: set ts=4 sw=4 expandtab: */