Add Android support for relative mouse mode to SDL.
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, jboolean relative);
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 vendor_id, jint product_id,
173 jboolean is_accelerometer, jint button_mask, 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;
226 static jmethodID midCreateCustomCursor;
227 static jmethodID midSetCustomCursor;
228 static jmethodID midSetSystemCursor;
229 static jmethodID midSupportsRelativeMouse;
230 static jmethodID midSetRelativeMouseEnabled;
233 static jclass mAudioManagerClass;
235 /* method signatures */
236 static jmethodID midAudioOpen;
237 static jmethodID midAudioWriteShortBuffer;
238 static jmethodID midAudioWriteByteBuffer;
239 static jmethodID midAudioClose;
240 static jmethodID midCaptureOpen;
241 static jmethodID midCaptureReadShortBuffer;
242 static jmethodID midCaptureReadByteBuffer;
243 static jmethodID midCaptureClose;
245 /* controller manager */
246 static jclass mControllerManagerClass;
248 /* method signatures */
249 static jmethodID midPollInputDevices;
250 static jmethodID midPollHapticDevices;
251 static jmethodID midHapticRun;
254 static jfieldID fidSeparateMouseAndTouch;
256 /* Accelerometer data storage */
257 static float fLastAccelerometer[3];
258 static SDL_bool bHasNewData;
260 static SDL_bool bHasEnvironmentVariables = SDL_FALSE;
262 /*******************************************************************************
263 Functions called by JNI
264 *******************************************************************************/
267 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
271 LOGI("JNI_OnLoad called");
272 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
273 LOGE("Failed to get the environment using GetEnv()");
277 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
278 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
280 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
281 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
283 Android_JNI_SetupThread();
285 return JNI_VERSION_1_4;
290 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
291 // We aren't fully initialized, let's just return.
298 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
299 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
301 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
303 Android_JNI_SetupThread();
305 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
307 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
308 "getNativeSurface","()Landroid/view/Surface;");
309 midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
310 "setActivityTitle","(Ljava/lang/String;)Z");
311 midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
312 "setWindowStyle","(Z)V");
313 midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
314 "setOrientation","(IIZLjava/lang/String;)V");
315 midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
316 "getContext","()Landroid/content/Context;");
317 midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
318 "isAndroidTV","()Z");
319 midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
320 "inputGetInputDeviceIds", "(I)[I");
321 midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
322 "sendMessage", "(II)Z");
323 midShowTextInput = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
324 "showTextInput", "(IIII)Z");
325 midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
326 "isScreenKeyboardShown","()Z");
327 midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
328 "clipboardSetText", "(Ljava/lang/String;)V");
329 midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
330 "clipboardGetText", "()Ljava/lang/String;");
331 midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
332 "clipboardHasText", "()Z");
333 midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
334 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
336 midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
337 "getManifestEnvironmentVariables", "()Z");
339 midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
340 midCreateCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "createCustomCursor", "([IIIII)I");
341 midSetCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setCustomCursor", "(I)Z");
342 midSetSystemCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setSystemCursor", "(I)Z");
344 midSupportsRelativeMouse = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "supportsRelativeMouse", "()Z");
345 midSetRelativeMouseEnabled = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setRelativeMouseEnabled", "(Z)Z");
347 if (!midGetNativeSurface ||
348 !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsAndroidTV || !midInputGetInputDeviceIds ||
349 !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown ||
350 !midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
351 !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables || !midGetDisplayDPI ||
352 !midCreateCustomCursor || !midSetCustomCursor || !midSetSystemCursor || !midSupportsRelativeMouse || !midSetRelativeMouseEnabled) {
353 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
356 fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
358 if (!fidSeparateMouseAndTouch) {
359 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
365 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
366 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
368 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
370 Android_JNI_SetupThread();
372 mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
374 midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
375 "audioOpen", "(IZZI)I");
376 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
377 "audioWriteShortBuffer", "([S)V");
378 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
379 "audioWriteByteBuffer", "([B)V");
380 midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
381 "audioClose", "()V");
382 midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
383 "captureOpen", "(IZZI)I");
384 midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
385 "captureReadShortBuffer", "([SZ)I");
386 midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
387 "captureReadByteBuffer", "([BZ)I");
388 midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
389 "captureClose", "()V");
391 if (!midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose ||
392 !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose) {
393 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
399 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
400 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
402 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
404 Android_JNI_SetupThread();
406 mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
408 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
409 "pollInputDevices", "()V");
410 midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
411 "pollHapticDevices", "()V");
412 midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
413 "hapticRun", "(II)V");
415 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun) {
416 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
422 /* SDL main function prototype */
423 typedef int (*SDL_main_func)(int argc, char *argv[]);
425 /* Start up the SDL app */
426 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
429 const char *library_file;
430 void *library_handle;
432 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
434 library_file = (*env)->GetStringUTFChars(env, library, NULL);
435 library_handle = dlopen(library_file, RTLD_GLOBAL);
436 if (library_handle) {
437 const char *function_name;
438 SDL_main_func SDL_main;
440 function_name = (*env)->GetStringUTFChars(env, function, NULL);
441 SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
448 /* Prepare the arguments. */
449 len = (*env)->GetArrayLength(env, array);
450 argv = SDL_stack_alloc(char*, 1 + len + 1);
452 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
453 https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
455 argv[argc++] = SDL_strdup("app_process");
456 for (i = 0; i < len; ++i) {
459 jstring string = (*env)->GetObjectArrayElement(env, array, i);
461 utf = (*env)->GetStringUTFChars(env, string, 0);
463 arg = SDL_strdup(utf);
464 (*env)->ReleaseStringUTFChars(env, string, utf);
466 (*env)->DeleteLocalRef(env, string);
469 arg = SDL_strdup("");
476 /* Run the application. */
477 status = SDL_main(argc, argv);
479 /* Release the arguments. */
480 for (i = 0; i < argc; ++i) {
483 SDL_stack_free(argv);
486 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
488 (*env)->ReleaseStringUTFChars(env, function, function_name);
490 dlclose(library_handle);
493 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
495 (*env)->ReleaseStringUTFChars(env, library, library_file);
497 /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
504 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
505 JNIEnv* env, jclass jcls,
508 const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
509 SDL_SendDropFile(NULL, path);
510 (*env)->ReleaseStringUTFChars(env, filename, path);
511 SDL_SendDropComplete(NULL);
515 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
516 JNIEnv* env, jclass jcls,
517 jint width, jint height, jint format, jfloat rate)
519 Android_SetScreenResolution(width, height, format, rate);
523 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
524 JNIEnv* env, jclass jcls,
525 jint device_id, jint keycode)
527 return Android_OnPadDown(device_id, keycode);
531 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
532 JNIEnv* env, jclass jcls,
533 jint device_id, jint keycode)
535 return Android_OnPadUp(device_id, keycode);
539 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
540 JNIEnv* env, jclass jcls,
541 jint device_id, jint axis, jfloat value)
543 Android_OnJoy(device_id, axis, value);
547 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
548 JNIEnv* env, jclass jcls,
549 jint device_id, jint hat_id, jint x, jint y)
551 Android_OnHat(device_id, hat_id, x, y);
555 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
556 JNIEnv* env, jclass jcls,
557 jint device_id, jstring device_name, jstring device_desc,
558 jint vendor_id, jint product_id, jboolean is_accelerometer,
559 jint button_mask, jint naxes, jint nhats, jint nballs)
562 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
563 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
565 retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer ? SDL_TRUE : SDL_FALSE, button_mask, naxes, nhats, nballs);
567 (*env)->ReleaseStringUTFChars(env, device_name, name);
568 (*env)->ReleaseStringUTFChars(env, device_desc, desc);
573 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
574 JNIEnv* env, jclass jcls,
577 return Android_RemoveJoystick(device_id);
580 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
581 JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
584 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
586 retval = Android_AddHaptic(device_id, name);
588 (*env)->ReleaseStringUTFChars(env, device_name, name);
593 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
594 JNIEnv* env, jclass jcls, jint device_id)
596 return Android_RemoveHaptic(device_id);
600 /* Surface Created */
601 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
603 SDL_WindowData *data;
604 SDL_VideoDevice *_this;
606 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
610 _this = SDL_GetVideoDevice();
611 data = (SDL_WindowData *) Android_Window->driverdata;
613 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
614 if (data->egl_surface == EGL_NO_SURFACE) {
615 if(data->native_window) {
616 ANativeWindow_release(data->native_window);
618 data->native_window = Android_JNI_GetNativeWindow();
619 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
622 /* GL Context handling is done in the event loop because this function is run from the Java thread */
626 /* Surface Destroyed */
627 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls)
629 /* We have to clear the current context and destroy the egl surface here
630 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
631 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
633 SDL_WindowData *data;
634 SDL_VideoDevice *_this;
636 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
640 _this = SDL_GetVideoDevice();
641 data = (SDL_WindowData *) Android_Window->driverdata;
643 if (data->egl_surface != EGL_NO_SURFACE) {
644 SDL_EGL_MakeCurrent(_this, NULL, NULL);
645 SDL_EGL_DestroySurface(_this, data->egl_surface);
646 data->egl_surface = EGL_NO_SURFACE;
649 /* GL Context handling is done in the event loop because this function is run from the Java thread */
654 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
655 JNIEnv* env, jclass jcls,
658 Android_OnKeyDown(keycode);
662 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
663 JNIEnv* env, jclass jcls,
666 Android_OnKeyUp(keycode);
669 /* Keyboard Focus Lost */
670 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
671 JNIEnv* env, jclass jcls)
673 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
679 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
680 JNIEnv* env, jclass jcls,
681 jint touch_device_id_in, jint pointer_finger_id_in,
682 jint action, jfloat x, jfloat y, jfloat p)
684 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
688 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
689 JNIEnv* env, jclass jcls,
690 jint button, jint action, jfloat x, jfloat y, jboolean relative)
692 Android_OnMouse(button, action, x, y, relative);
696 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
697 JNIEnv* env, jclass jcls,
698 jfloat x, jfloat y, jfloat z)
700 fLastAccelerometer[0] = x;
701 fLastAccelerometer[1] = y;
702 fLastAccelerometer[2] = z;
703 bHasNewData = SDL_TRUE;
707 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
708 JNIEnv* env, jclass jcls)
710 SDL_SendClipboardUpdate();
714 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
715 JNIEnv* env, jclass cls)
717 SDL_SendAppEvent(SDL_APP_LOWMEMORY);
721 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
722 JNIEnv* env, jclass cls)
724 /* Discard previous events. The user should have handled state storage
725 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
726 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
727 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
728 /* Inject a SDL_QUIT event */
730 SDL_SendAppEvent(SDL_APP_TERMINATING);
731 /* Resume the event loop so that the app can catch SDL_QUIT which
732 * should now be the top event in the event queue. */
733 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
737 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
738 JNIEnv* env, jclass cls)
740 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
742 if (Android_Window) {
743 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
744 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
745 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
746 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
748 /* *After* sending the relevant events, signal the pause semaphore
749 * so the event loop knows to pause and (optionally) block itself */
750 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
755 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
756 JNIEnv* env, jclass cls)
758 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
760 if (Android_Window) {
761 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
762 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
763 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
764 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
765 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
766 * We can't restore the GL Context here because it needs to be done on the SDL main thread
767 * and this function will be called from the Java thread instead.
769 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
773 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
774 JNIEnv* env, jclass cls,
775 jstring text, jint newCursorPosition)
777 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
779 SDL_SendKeyboardText(utftext);
781 (*env)->ReleaseStringUTFChars(env, text, utftext);
784 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
785 JNIEnv* env, jclass cls,
788 SDL_Scancode code = SDL_SCANCODE_UNKNOWN;
791 // We do not care about bigger than 127.
792 if (chUnicode < 127) {
793 AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode];
798 if (mod & KMOD_SHIFT) {
799 /* If character uses shift, press shift down */
800 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
803 /* send a keydown and keyup even for the character */
804 SDL_SendKeyboardKey(SDL_PRESSED, code);
805 SDL_SendKeyboardKey(SDL_RELEASED, code);
807 if (mod & KMOD_SHIFT) {
808 /* If character uses shift, press shift back up */
809 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
814 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
815 JNIEnv* env, jclass cls,
816 jstring text, jint newCursorPosition)
818 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
820 SDL_SendEditingText(utftext, 0, 0);
822 (*env)->ReleaseStringUTFChars(env, text, utftext);
825 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
826 JNIEnv* env, jclass cls,
829 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
830 const char *hint = SDL_GetHint(utfname);
832 jstring result = (*env)->NewStringUTF(env, hint);
833 (*env)->ReleaseStringUTFChars(env, name, utfname);
838 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
839 JNIEnv* env, jclass cls,
840 jstring name, jstring value)
842 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
843 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
845 SDL_setenv(utfname, utfvalue, 1);
847 (*env)->ReleaseStringUTFChars(env, name, utfname);
848 (*env)->ReleaseStringUTFChars(env, value, utfvalue);
852 /*******************************************************************************
853 Functions called by SDL into Java
854 *******************************************************************************/
856 static int s_active = 0;
857 struct LocalReferenceHolder
863 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
865 struct LocalReferenceHolder refholder;
866 refholder.m_env = NULL;
867 refholder.m_func = func;
869 SDL_Log("Entering function %s", func);
874 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
876 const int capacity = 16;
877 if ((*env)->PushLocalFrame(env, capacity) < 0) {
878 SDL_SetError("Failed to allocate enough JVM local references");
882 refholder->m_env = env;
886 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
889 SDL_Log("Leaving function %s", refholder->m_func);
891 if (refholder->m_env) {
892 JNIEnv* env = refholder->m_env;
893 (*env)->PopLocalFrame(env, NULL);
898 static SDL_bool LocalReferenceHolder_IsActive(void)
903 ANativeWindow* Android_JNI_GetNativeWindow(void)
907 JNIEnv *env = Android_JNI_GetEnv();
909 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
910 anw = ANativeWindow_fromSurface(env, s);
911 (*env)->DeleteLocalRef(env, s);
916 void Android_JNI_SetActivityTitle(const char *title)
918 JNIEnv *mEnv = Android_JNI_GetEnv();
920 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
921 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle);
922 (*mEnv)->DeleteLocalRef(mEnv, jtitle);
925 void Android_JNI_SetWindowStyle(SDL_bool fullscreen)
927 JNIEnv *mEnv = Android_JNI_GetEnv();
928 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
931 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
933 JNIEnv *mEnv = Android_JNI_GetEnv();
935 jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
936 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
937 (*mEnv)->DeleteLocalRef(mEnv, jhint);
940 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
943 SDL_bool retval = SDL_FALSE;
946 for (i = 0; i < 3; ++i) {
947 values[i] = fLastAccelerometer[i];
949 bHasNewData = SDL_FALSE;
956 static void Android_JNI_ThreadDestroyed(void* value)
958 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
959 JNIEnv *env = (JNIEnv*) value;
961 (*mJavaVM)->DetachCurrentThread(mJavaVM);
962 pthread_setspecific(mThreadKey, NULL);
966 JNIEnv* Android_JNI_GetEnv(void)
968 /* From http://developer.android.com/guide/practices/jni.html
969 * All threads are Linux threads, scheduled by the kernel.
970 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
971 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
972 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
973 * and cannot make JNI calls.
974 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
975 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
977 * Note: You can call this function any number of times for the same thread, there's no harm in it
981 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
983 LOGE("failed to attach current thread");
987 /* From http://developer.android.com/guide/practices/jni.html
988 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
989 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
990 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
991 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
992 * Note: The destructor is not called unless the stored value is != NULL
993 * Note: You can call this function any number of times for the same thread, there's no harm in it
994 * (except for some lost CPU cycles)
996 pthread_setspecific(mThreadKey, (void*) env);
1001 int Android_JNI_SetupThread(void)
1003 Android_JNI_GetEnv();
1010 static jboolean audioBuffer16Bit = JNI_FALSE;
1011 static jobject audioBuffer = NULL;
1012 static void* audioBufferPinned = NULL;
1013 static jboolean captureBuffer16Bit = JNI_FALSE;
1014 static jobject captureBuffer = NULL;
1016 int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
1018 jboolean audioBufferStereo;
1019 int audioBufferFrames;
1020 jobject jbufobj = NULL;
1023 JNIEnv *env = Android_JNI_GetEnv();
1026 LOGE("callback_handler: failed to attach current thread");
1028 Android_JNI_SetupThread();
1030 audioBufferStereo = channelCount > 1;
1033 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
1034 captureBuffer16Bit = is16Bit;
1035 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1036 /* Error during audio initialization */
1037 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
1041 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
1042 audioBuffer16Bit = is16Bit;
1043 if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1044 /* Error during audio initialization */
1045 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
1050 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
1051 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
1054 jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1055 if (audioBufferLocal) {
1056 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1057 (*env)->DeleteLocalRef(env, audioBufferLocal);
1061 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1062 if (audioBufferLocal) {
1063 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1064 (*env)->DeleteLocalRef(env, audioBufferLocal);
1068 if (jbufobj == NULL) {
1069 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
1074 captureBuffer = jbufobj;
1076 audioBuffer = jbufobj;
1083 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
1085 audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
1088 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
1090 audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
1093 if (audioBufferStereo) {
1094 audioBufferFrames /= 2;
1097 return audioBufferFrames;
1100 int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi)
1102 JNIEnv *env = Android_JNI_GetEnv();
1104 jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI);
1105 jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj);
1107 jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F");
1108 jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F");
1109 jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I");
1111 float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi);
1112 float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi);
1113 int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi);
1116 (*env)->DeleteLocalRef(env, jDisplayObj);
1117 (*env)->DeleteLocalRef(env, jDisplayClass);
1120 *ddpi = (float)nativeDdpi;
1132 void * Android_JNI_GetAudioBuffer(void)
1134 return audioBufferPinned;
1137 void Android_JNI_WriteAudioBuffer(void)
1139 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
1141 if (audioBuffer16Bit) {
1142 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
1143 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
1145 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
1146 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
1149 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
1152 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
1154 JNIEnv *env = Android_JNI_GetEnv();
1155 jboolean isCopy = JNI_FALSE;
1158 if (captureBuffer16Bit) {
1159 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
1160 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
1162 jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
1164 SDL_memcpy(buffer, ptr, br);
1165 (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
1168 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
1169 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
1171 jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
1172 SDL_memcpy(buffer, ptr, br);
1173 (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
1180 void Android_JNI_FlushCapturedAudio(void)
1182 JNIEnv *env = Android_JNI_GetEnv();
1183 #if 0 /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
1184 if (captureBuffer16Bit) {
1185 const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
1186 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1188 const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
1189 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1192 if (captureBuffer16Bit) {
1193 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
1195 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
1200 void Android_JNI_CloseAudioDevice(const int iscapture)
1202 JNIEnv *env = Android_JNI_GetEnv();
1205 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
1206 if (captureBuffer) {
1207 (*env)->DeleteGlobalRef(env, captureBuffer);
1208 captureBuffer = NULL;
1211 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
1213 (*env)->DeleteGlobalRef(env, audioBuffer);
1215 audioBufferPinned = NULL;
1220 /* Test for an exception and call SDL_SetError with its detail if one occurs */
1221 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
1222 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
1224 JNIEnv *mEnv = Android_JNI_GetEnv();
1225 jthrowable exception;
1227 SDL_assert(LocalReferenceHolder_IsActive());
1229 exception = (*mEnv)->ExceptionOccurred(mEnv);
1230 if (exception != NULL) {
1233 /* Until this happens most JNI operations have undefined behaviour */
1234 (*mEnv)->ExceptionClear(mEnv);
1237 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
1238 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
1239 jstring exceptionName;
1240 const char* exceptionNameUTF8;
1241 jstring exceptionMessage;
1243 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
1244 exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
1245 exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
1247 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
1248 exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
1250 if (exceptionMessage != NULL) {
1251 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
1252 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
1253 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
1255 SDL_SetError("%s", exceptionNameUTF8);
1258 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
1267 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
1269 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1275 jobject assetManager;
1276 jobject inputStream;
1278 jobject readableByteChannel;
1279 jstring fileNameJString;
1282 jfieldID descriptor;
1284 JNIEnv *mEnv = Android_JNI_GetEnv();
1285 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1289 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
1290 ctx->hidden.androidio.position = 0;
1292 /* context = SDLActivity.getContext(); */
1293 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext);
1295 /* assetManager = context.getAssets(); */
1296 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
1297 "getAssets", "()Landroid/content/res/AssetManager;");
1298 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
1300 /* First let's try opening the file to obtain an AssetFileDescriptor.
1301 * This method reads the files directly from the APKs using standard *nix calls
1303 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
1304 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
1305 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1309 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
1310 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1311 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1315 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
1316 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1317 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1321 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
1322 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
1323 fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
1324 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
1325 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
1326 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1328 /* Seek to the correct offset in the file. */
1329 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
1333 /* Disabled log message because of spam on the Nexus 7 */
1334 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
1336 /* Try the old method using InputStream */
1337 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1339 /* inputStream = assetManager.open(<filename>); */
1340 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
1341 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
1342 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
1343 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1344 /* Try fallback to APK expansion files */
1345 inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
1347 /* Exception is checked first because it always needs to be cleared.
1348 * If no exception occurred then the last SDL error message is kept.
1350 if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
1355 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1357 /* Despite all the visible documentation on [Asset]InputStream claiming
1358 * that the .available() method is not guaranteed to return the entire file
1359 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
1360 * android/apis/content/ReadAsset.java imply that Android's
1361 * AssetInputStream.available() /will/ always return the total file size
1364 /* size = inputStream.available(); */
1365 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1366 "available", "()I");
1367 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
1368 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1372 /* readableByteChannel = Channels.newChannel(inputStream); */
1373 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
1374 mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
1376 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
1377 readableByteChannel = (*mEnv)->CallStaticObjectMethod(
1378 mEnv, channels, mid, inputStream);
1379 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1383 ctx->hidden.androidio.readableByteChannelRef =
1384 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
1386 /* Store .read id for reading purposes */
1387 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
1388 "read", "(Ljava/nio/ByteBuffer;)I");
1389 ctx->hidden.androidio.readMethod = mid;
1396 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1398 if(ctx->hidden.androidio.inputStreamRef != NULL) {
1399 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1402 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
1403 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1406 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
1407 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1412 LocalReferenceHolder_Cleanup(&refs);
1416 int Android_JNI_FileOpen(SDL_RWops* ctx,
1417 const char* fileName, const char* mode)
1419 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1420 JNIEnv *mEnv = Android_JNI_GetEnv();
1422 jstring fileNameJString;
1424 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1425 LocalReferenceHolder_Cleanup(&refs);
1430 LocalReferenceHolder_Cleanup(&refs);
1434 fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
1435 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
1436 ctx->hidden.androidio.inputStreamRef = NULL;
1437 ctx->hidden.androidio.readableByteChannelRef = NULL;
1438 ctx->hidden.androidio.readMethod = NULL;
1439 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1441 retval = Internal_Android_JNI_FileOpen(ctx);
1442 LocalReferenceHolder_Cleanup(&refs);
1446 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
1447 size_t size, size_t maxnum)
1449 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1451 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1452 size_t bytesMax = size * maxnum;
1454 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
1455 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
1457 result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
1459 ctx->hidden.androidio.position += result;
1460 LocalReferenceHolder_Cleanup(&refs);
1461 return result / size;
1463 LocalReferenceHolder_Cleanup(&refs);
1466 jlong bytesRemaining = (jlong) (size * maxnum);
1467 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
1470 jobject readableByteChannel;
1471 jmethodID readMethod;
1474 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
1475 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
1477 mEnv = Android_JNI_GetEnv();
1478 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1479 LocalReferenceHolder_Cleanup(&refs);
1483 readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
1484 readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
1485 byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
1487 while (bytesRemaining > 0) {
1488 /* result = readableByteChannel.read(...); */
1489 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
1491 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1492 LocalReferenceHolder_Cleanup(&refs);
1500 bytesRemaining -= result;
1501 bytesRead += result;
1502 ctx->hidden.androidio.position += result;
1504 LocalReferenceHolder_Cleanup(&refs);
1505 return bytesRead / size;
1509 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
1510 size_t size, size_t num)
1512 SDL_SetError("Cannot write to Android package filesystem");
1516 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
1518 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1521 JNIEnv *mEnv = Android_JNI_GetEnv();
1523 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1524 LocalReferenceHolder_Cleanup(&refs);
1525 return SDL_SetError("Failed to allocate enough JVM local references");
1530 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1533 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1534 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
1535 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1537 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1538 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1539 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1544 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
1546 /* inputStream.close(); */
1547 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1549 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1550 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1551 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1552 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1562 LocalReferenceHolder_Cleanup(&refs);
1567 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
1569 return ctx->hidden.androidio.size;
1572 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
1574 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1578 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1579 offset += ctx->hidden.androidio.offset;
1582 offset += ctx->hidden.androidio.position;
1583 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1584 offset += ctx->hidden.androidio.offset;
1587 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
1590 return SDL_SetError("Unknown value for 'whence'");
1594 ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
1595 if (ret == -1) return -1;
1596 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
1603 newPosition = offset;
1606 newPosition = ctx->hidden.androidio.position + offset;
1609 newPosition = ctx->hidden.androidio.size + offset;
1612 return SDL_SetError("Unknown value for 'whence'");
1615 /* Validate the new position */
1616 if (newPosition < 0) {
1617 return SDL_Error(SDL_EFSEEK);
1619 if (newPosition > ctx->hidden.androidio.size) {
1620 newPosition = ctx->hidden.androidio.size;
1623 movement = newPosition - ctx->hidden.androidio.position;
1625 unsigned char buffer[4096];
1627 /* The easy case where we're seeking forwards */
1628 while (movement > 0) {
1629 Sint64 amount = sizeof (buffer);
1631 if (amount > movement) {
1634 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
1636 /* Failed to read/skip the required amount, so fail */
1643 } else if (movement < 0) {
1644 /* We can't seek backwards so we have to reopen the file and seek */
1645 /* forwards which obviously isn't very efficient */
1646 Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
1647 Internal_Android_JNI_FileOpen(ctx);
1648 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1652 return ctx->hidden.androidio.position;
1656 int Android_JNI_FileClose(SDL_RWops* ctx)
1658 return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
1661 int Android_JNI_SetClipboardText(const char* text)
1663 JNIEnv* env = Android_JNI_GetEnv();
1664 jstring string = (*env)->NewStringUTF(env, text);
1665 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
1666 (*env)->DeleteLocalRef(env, string);
1670 char* Android_JNI_GetClipboardText(void)
1672 JNIEnv* env = Android_JNI_GetEnv();
1676 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
1678 const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1680 text = SDL_strdup(utf);
1681 (*env)->ReleaseStringUTFChars(env, string, utf);
1683 (*env)->DeleteLocalRef(env, string);
1686 return (text == NULL) ? SDL_strdup("") : text;
1689 SDL_bool Android_JNI_HasClipboardText(void)
1691 JNIEnv* env = Android_JNI_GetEnv();
1692 jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
1693 return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
1696 /* returns 0 on success or -1 on error (others undefined then)
1697 * returns truthy or falsy value in plugged, charged and battery
1698 * returns the value in seconds and percent or -1 if not available
1700 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1702 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1703 JNIEnv* env = Android_JNI_GetEnv();
1714 if (!LocalReferenceHolder_Init(&refs, env)) {
1715 LocalReferenceHolder_Cleanup(&refs);
1720 /* context = SDLActivity.getContext(); */
1721 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1723 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1725 cls = (*env)->FindClass(env, "android/content/IntentFilter");
1727 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1728 filter = (*env)->NewObject(env, cls, mid, action);
1730 (*env)->DeleteLocalRef(env, action);
1732 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1733 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1735 (*env)->DeleteLocalRef(env, filter);
1737 cls = (*env)->GetObjectClass(env, intent);
1739 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1741 /* Watch out for C89 scoping rules because of the macro */
1742 #define GET_INT_EXTRA(var, key) \
1744 iname = (*env)->NewStringUTF(env, key); \
1745 var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1746 (*env)->DeleteLocalRef(env, iname);
1748 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1750 /* Watch out for C89 scoping rules because of the macro */
1751 #define GET_BOOL_EXTRA(var, key) \
1753 bname = (*env)->NewStringUTF(env, key); \
1754 var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1755 (*env)->DeleteLocalRef(env, bname);
1758 /* Watch out for C89 scoping rules because of the macro */
1759 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1761 LocalReferenceHolder_Cleanup(&refs);
1764 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1765 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1766 *plugged = (0 < plug) ? 1 : 0;
1770 /* Watch out for C89 scoping rules because of the macro */
1771 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1773 LocalReferenceHolder_Cleanup(&refs);
1776 /* 5 == BatteryManager.BATTERY_STATUS_FULL */
1777 *charged = (status == 5) ? 1 : 0;
1781 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1782 *battery = present ? 1 : 0;
1786 *seconds = -1; /* not possible */
1793 /* Watch out for C89 scoping rules because of the macro */
1795 GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1798 /* Watch out for C89 scoping rules because of the macro */
1800 GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1804 if ((level == -1) || (scale == -1)) {
1805 LocalReferenceHolder_Cleanup(&refs);
1808 *percent = level * 100 / scale;
1811 (*env)->DeleteLocalRef(env, intent);
1813 LocalReferenceHolder_Cleanup(&refs);
1817 /* returns number of found touch devices as return value and ids in parameter ids */
1818 int Android_JNI_GetTouchDeviceIds(int **ids) {
1819 JNIEnv *env = Android_JNI_GetEnv();
1820 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1821 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources);
1825 number = (int) (*env)->GetArrayLength(env, array);
1827 jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1830 *ids = SDL_malloc(number * sizeof (**ids));
1831 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1832 (*ids)[i] = elements[i];
1834 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1837 (*env)->DeleteLocalRef(env, array);
1842 /* sets the mSeparateMouseAndTouch field */
1843 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
1845 JNIEnv *env = Android_JNI_GetEnv();
1846 (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
1849 void Android_JNI_PollInputDevices(void)
1851 JNIEnv *env = Android_JNI_GetEnv();
1852 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
1855 void Android_JNI_PollHapticDevices(void)
1857 JNIEnv *env = Android_JNI_GetEnv();
1858 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
1861 void Android_JNI_HapticRun(int device_id, int length)
1863 JNIEnv *env = Android_JNI_GetEnv();
1864 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, length);
1868 /* See SDLActivity.java for constants. */
1869 #define COMMAND_SET_KEEP_SCREEN_ON 5
1871 /* sends message to be handled on the UI event dispatch thread */
1872 int Android_JNI_SendMessage(int command, int param)
1874 JNIEnv *env = Android_JNI_GetEnv();
1876 success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
1877 return success ? 0 : -1;
1880 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
1882 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
1885 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1887 JNIEnv *env = Android_JNI_GetEnv();
1888 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
1895 void Android_JNI_HideTextInput(void)
1897 /* has to match Activity constant */
1898 const int COMMAND_TEXTEDIT_HIDE = 3;
1899 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1902 SDL_bool Android_JNI_IsScreenKeyboardShown()
1904 JNIEnv *mEnv = Android_JNI_GetEnv();
1905 jboolean is_shown = 0;
1906 is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown);
1911 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
1919 jintArray button_flags;
1920 jintArray button_ids;
1921 jobjectArray button_texts;
1927 env = Android_JNI_GetEnv();
1929 /* convert parameters */
1931 clazz = (*env)->FindClass(env, "java/lang/String");
1933 title = (*env)->NewStringUTF(env, messageboxdata->title);
1934 message = (*env)->NewStringUTF(env, messageboxdata->message);
1936 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1937 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1938 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
1940 for (i = 0; i < messageboxdata->numbuttons; ++i) {
1941 temp = messageboxdata->buttons[i].flags;
1942 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
1943 temp = messageboxdata->buttons[i].buttonid;
1944 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
1945 text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
1946 (*env)->SetObjectArrayElement(env, button_texts, i, text);
1947 (*env)->DeleteLocalRef(env, text);
1950 if (messageboxdata->colorScheme) {
1951 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
1952 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
1953 temp = (0xFF << 24) |
1954 (messageboxdata->colorScheme->colors[i].r << 16) |
1955 (messageboxdata->colorScheme->colors[i].g << 8) |
1956 (messageboxdata->colorScheme->colors[i].b << 0);
1957 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
1963 (*env)->DeleteLocalRef(env, clazz);
1965 /* context = SDLActivity.getContext(); */
1966 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1968 clazz = (*env)->GetObjectClass(env, context);
1970 mid = (*env)->GetMethodID(env, clazz,
1971 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
1972 *buttonid = (*env)->CallIntMethod(env, context, mid,
1973 messageboxdata->flags,
1981 (*env)->DeleteLocalRef(env, context);
1982 (*env)->DeleteLocalRef(env, clazz);
1984 /* delete parameters */
1986 (*env)->DeleteLocalRef(env, title);
1987 (*env)->DeleteLocalRef(env, message);
1988 (*env)->DeleteLocalRef(env, button_flags);
1989 (*env)->DeleteLocalRef(env, button_ids);
1990 (*env)->DeleteLocalRef(env, button_texts);
1991 (*env)->DeleteLocalRef(env, colors);
1997 //////////////////////////////////////////////////////////////////////////////
1999 // Functions exposed to SDL applications in SDL_system.h
2000 //////////////////////////////////////////////////////////////////////////////
2003 void *SDL_AndroidGetJNIEnv(void)
2005 return Android_JNI_GetEnv();
2008 void *SDL_AndroidGetActivity(void)
2010 /* See SDL_system.h for caveats on using this function. */
2012 JNIEnv *env = Android_JNI_GetEnv();
2017 /* return SDLActivity.getContext(); */
2018 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2021 SDL_bool SDL_IsAndroidTV(void)
2023 JNIEnv *env = Android_JNI_GetEnv();
2024 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
2027 const char * SDL_AndroidGetInternalStoragePath(void)
2029 static char *s_AndroidInternalFilesPath = NULL;
2031 if (!s_AndroidInternalFilesPath) {
2032 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2039 JNIEnv *env = Android_JNI_GetEnv();
2040 if (!LocalReferenceHolder_Init(&refs, env)) {
2041 LocalReferenceHolder_Cleanup(&refs);
2045 /* context = SDLActivity.getContext(); */
2046 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2048 SDL_SetError("Couldn't get Android context!");
2049 LocalReferenceHolder_Cleanup(&refs);
2053 /* fileObj = context.getFilesDir(); */
2054 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2055 "getFilesDir", "()Ljava/io/File;");
2056 fileObject = (*env)->CallObjectMethod(env, context, mid);
2058 SDL_SetError("Couldn't get internal directory");
2059 LocalReferenceHolder_Cleanup(&refs);
2063 /* path = fileObject.getCanonicalPath(); */
2064 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2065 "getCanonicalPath", "()Ljava/lang/String;");
2066 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2067 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
2068 LocalReferenceHolder_Cleanup(&refs);
2072 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2073 s_AndroidInternalFilesPath = SDL_strdup(path);
2074 (*env)->ReleaseStringUTFChars(env, pathString, path);
2076 LocalReferenceHolder_Cleanup(&refs);
2078 return s_AndroidInternalFilesPath;
2081 int SDL_AndroidGetExternalStorageState(void)
2083 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2086 jstring stateString;
2090 JNIEnv *env = Android_JNI_GetEnv();
2091 if (!LocalReferenceHolder_Init(&refs, env)) {
2092 LocalReferenceHolder_Cleanup(&refs);
2096 cls = (*env)->FindClass(env, "android/os/Environment");
2097 mid = (*env)->GetStaticMethodID(env, cls,
2098 "getExternalStorageState", "()Ljava/lang/String;");
2099 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
2101 state = (*env)->GetStringUTFChars(env, stateString, NULL);
2103 /* Print an info message so people debugging know the storage state */
2104 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
2106 if (SDL_strcmp(state, "mounted") == 0) {
2107 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
2108 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
2109 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
2110 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
2114 (*env)->ReleaseStringUTFChars(env, stateString, state);
2116 LocalReferenceHolder_Cleanup(&refs);
2120 const char * SDL_AndroidGetExternalStoragePath(void)
2122 static char *s_AndroidExternalFilesPath = NULL;
2124 if (!s_AndroidExternalFilesPath) {
2125 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2132 JNIEnv *env = Android_JNI_GetEnv();
2133 if (!LocalReferenceHolder_Init(&refs, env)) {
2134 LocalReferenceHolder_Cleanup(&refs);
2138 /* context = SDLActivity.getContext(); */
2139 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2141 /* fileObj = context.getExternalFilesDir(); */
2142 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2143 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
2144 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2146 SDL_SetError("Couldn't get external directory");
2147 LocalReferenceHolder_Cleanup(&refs);
2151 /* path = fileObject.getAbsolutePath(); */
2152 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2153 "getAbsolutePath", "()Ljava/lang/String;");
2154 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2156 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2157 s_AndroidExternalFilesPath = SDL_strdup(path);
2158 (*env)->ReleaseStringUTFChars(env, pathString, path);
2160 LocalReferenceHolder_Cleanup(&refs);
2162 return s_AndroidExternalFilesPath;
2165 void Android_JNI_GetManifestEnvironmentVariables(void)
2167 if (!mActivityClass || !midGetManifestEnvironmentVariables) {
2168 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
2172 if (!bHasEnvironmentVariables) {
2173 JNIEnv *env = Android_JNI_GetEnv();
2174 SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
2176 bHasEnvironmentVariables = SDL_TRUE;
2181 int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
2183 JNIEnv *mEnv = Android_JNI_GetEnv();
2184 int custom_cursor = 0;
2186 pixels = (*mEnv)->NewIntArray(mEnv, surface->w * surface->h);
2188 (*mEnv)->SetIntArrayRegion(mEnv, pixels, 0, surface->w * surface->h, (int *)surface->pixels);
2189 custom_cursor = (*mEnv)->CallStaticIntMethod(mEnv, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y);
2190 (*mEnv)->DeleteLocalRef(mEnv, pixels);
2194 return custom_cursor;
2198 SDL_bool Android_JNI_SetCustomCursor(int cursorID)
2200 JNIEnv *mEnv = Android_JNI_GetEnv();
2201 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetCustomCursor, cursorID);
2204 SDL_bool Android_JNI_SetSystemCursor(int cursorID)
2206 JNIEnv *mEnv = Android_JNI_GetEnv();
2207 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetSystemCursor, cursorID);
2210 SDL_bool Android_JNI_SupportsRelativeMouse()
2212 JNIEnv *mEnv = Android_JNI_GetEnv();
2213 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSupportsRelativeMouse);
2216 SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled)
2218 JNIEnv *mEnv = Android_JNI_GetEnv();
2219 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
2223 #endif /* __ANDROID__ */
2225 /* vi: set ts=4 sw=4 expandtab: */