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)
64 /* Audio encoding definitions */
65 #define ENCODING_PCM_8BIT 3
66 #define ENCODING_PCM_16BIT 2
67 #define ENCODING_PCM_FLOAT 4
69 /* Java class SDLActivity */
70 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
71 JNIEnv* mEnv, jclass cls);
73 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
74 JNIEnv* env, jclass cls,
75 jstring library, jstring function, jobject array);
77 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
78 JNIEnv* env, jclass jcls,
81 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
82 JNIEnv* env, jclass jcls,
83 jint surfaceWidth, jint surfaceHeight,
84 jint deviceWidth, jint deviceHeight, jint format, jfloat rate);
86 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
87 JNIEnv* env, jclass jcls);
89 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
90 JNIEnv* env, jclass jcls);
92 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
93 JNIEnv* env, jclass jcls,
96 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
97 JNIEnv* env, jclass jcls,
100 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
101 JNIEnv* env, jclass jcls);
103 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
104 JNIEnv* env, jclass jcls,
105 jint touch_device_id_in, jint pointer_finger_id_in,
106 jint action, jfloat x, jfloat y, jfloat p);
108 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
109 JNIEnv* env, jclass jcls,
110 jint button, jint action, jfloat x, jfloat y, jboolean relative);
112 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
113 JNIEnv* env, jclass jcls,
114 jfloat x, jfloat y, jfloat z);
116 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
117 JNIEnv* env, jclass jcls);
119 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
120 JNIEnv* env, jclass cls);
122 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
123 JNIEnv* env, jclass cls);
125 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
126 JNIEnv* env, jclass cls);
128 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
129 JNIEnv* env, jclass cls);
131 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
132 JNIEnv* env, jclass cls,
135 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
136 JNIEnv* env, jclass cls,
137 jstring name, jstring value);
139 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeEnvironmentVariablesSet)(
140 JNIEnv* env, jclass cls);
142 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)(
143 JNIEnv* env, jclass cls,
146 /* Java class SDLInputConnection */
147 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
148 JNIEnv* env, jclass cls,
149 jstring text, jint newCursorPosition);
151 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
152 JNIEnv* env, jclass cls,
155 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
156 JNIEnv* env, jclass cls,
157 jstring text, jint newCursorPosition);
159 /* Java class SDLAudioManager */
160 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
161 JNIEnv *env, jclass jcls);
163 /* Java class SDLControllerManager */
164 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
165 JNIEnv *env, jclass jcls);
167 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
168 JNIEnv* env, jclass jcls,
169 jint device_id, jint keycode);
171 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
172 JNIEnv* env, jclass jcls,
173 jint device_id, jint keycode);
175 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
176 JNIEnv* env, jclass jcls,
177 jint device_id, jint axis, jfloat value);
179 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
180 JNIEnv* env, jclass jcls,
181 jint device_id, jint hat_id, jint x, jint y);
183 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
184 JNIEnv* env, jclass jcls,
185 jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id,
186 jboolean is_accelerometer, jint button_mask, jint naxes, jint nhats, jint nballs);
188 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
189 JNIEnv* env, jclass jcls,
192 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
193 JNIEnv* env, jclass jcls,
194 jint device_id, jstring device_name);
196 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
197 JNIEnv* env, jclass jcls,
202 /* Uncomment this to log messages entering and exiting methods in this file */
203 /* #define DEBUG_JNI */
205 static void Android_JNI_ThreadDestroyed(void*);
206 static void checkJNIReady(void);
208 /*******************************************************************************
209 This file links the Java side of Android with libsdl
210 *******************************************************************************/
214 /*******************************************************************************
216 *******************************************************************************/
217 static pthread_key_t mThreadKey;
218 static JavaVM* mJavaVM;
221 static jclass mActivityClass;
223 /* method signatures */
224 static jmethodID midGetNativeSurface;
225 static jmethodID midSetActivityTitle;
226 static jmethodID midSetWindowStyle;
227 static jmethodID midSetOrientation;
228 static jmethodID midGetContext;
229 static jmethodID midIsTablet;
230 static jmethodID midIsAndroidTV;
231 static jmethodID midIsChromebook;
232 static jmethodID midIsDeXMode;
233 static jmethodID midManualBackButton;
234 static jmethodID midInputGetInputDeviceIds;
235 static jmethodID midSendMessage;
236 static jmethodID midShowTextInput;
237 static jmethodID midIsScreenKeyboardShown;
238 static jmethodID midClipboardSetText;
239 static jmethodID midClipboardGetText;
240 static jmethodID midClipboardHasText;
241 static jmethodID midOpenAPKExpansionInputStream;
242 static jmethodID midGetManifestEnvironmentVariables;
243 static jmethodID midGetDisplayDPI;
244 static jmethodID midCreateCustomCursor;
245 static jmethodID midSetCustomCursor;
246 static jmethodID midSetSystemCursor;
247 static jmethodID midSupportsRelativeMouse;
248 static jmethodID midSetRelativeMouseEnabled;
251 static jclass mAudioManagerClass;
253 /* method signatures */
254 static jmethodID midAudioOpen;
255 static jmethodID midAudioWriteByteBuffer;
256 static jmethodID midAudioWriteShortBuffer;
257 static jmethodID midAudioWriteFloatBuffer;
258 static jmethodID midAudioClose;
259 static jmethodID midCaptureOpen;
260 static jmethodID midCaptureReadByteBuffer;
261 static jmethodID midCaptureReadShortBuffer;
262 static jmethodID midCaptureReadFloatBuffer;
263 static jmethodID midCaptureClose;
265 /* controller manager */
266 static jclass mControllerManagerClass;
268 /* method signatures */
269 static jmethodID midPollInputDevices;
270 static jmethodID midPollHapticDevices;
271 static jmethodID midHapticRun;
272 static jmethodID midHapticStop;
275 static jfieldID fidSeparateMouseAndTouch;
277 /* Accelerometer data storage */
278 static float fLastAccelerometer[3];
279 static SDL_bool bHasNewData;
281 static SDL_bool bHasEnvironmentVariables = SDL_FALSE;
283 /*******************************************************************************
284 Functions called by JNI
285 *******************************************************************************/
288 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
292 LOGI("JNI_OnLoad called");
293 if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
294 LOGE("Failed to get the environment using GetEnv()");
298 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
299 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
301 if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
302 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
304 Android_JNI_SetupThread();
306 return JNI_VERSION_1_4;
311 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
312 // We aren't fully initialized, let's just return.
319 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
320 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
322 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
324 Android_JNI_SetupThread();
326 mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
328 midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
329 "getNativeSurface","()Landroid/view/Surface;");
330 midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
331 "setActivityTitle","(Ljava/lang/String;)Z");
332 midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
333 "setWindowStyle","(Z)V");
334 midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
335 "setOrientation","(IIZLjava/lang/String;)V");
336 midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
337 "getContext","()Landroid/content/Context;");
338 midIsTablet = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
340 midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
341 "isAndroidTV","()Z");
342 midIsChromebook = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
343 "isChromebook", "()Z");
344 midIsDeXMode = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
346 midManualBackButton = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
347 "manualBackButton", "()V");
348 midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
349 "inputGetInputDeviceIds", "(I)[I");
350 midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
351 "sendMessage", "(II)Z");
352 midShowTextInput = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
353 "showTextInput", "(IIII)Z");
354 midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
355 "isScreenKeyboardShown","()Z");
356 midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
357 "clipboardSetText", "(Ljava/lang/String;)V");
358 midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
359 "clipboardGetText", "()Ljava/lang/String;");
360 midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
361 "clipboardHasText", "()Z");
362 midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
363 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
365 midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
366 "getManifestEnvironmentVariables", "()Z");
368 midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
369 midCreateCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "createCustomCursor", "([IIIII)I");
370 midSetCustomCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setCustomCursor", "(I)Z");
371 midSetSystemCursor = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setSystemCursor", "(I)Z");
373 midSupportsRelativeMouse = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "supportsRelativeMouse", "()Z");
374 midSetRelativeMouseEnabled = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "setRelativeMouseEnabled", "(Z)Z");
377 if (!midGetNativeSurface ||
378 !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsTablet || !midIsAndroidTV || !midInputGetInputDeviceIds ||
379 !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown ||
380 !midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
381 !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables || !midGetDisplayDPI ||
382 !midCreateCustomCursor || !midSetCustomCursor || !midSetSystemCursor || !midSupportsRelativeMouse || !midSetRelativeMouseEnabled ||
383 !midIsChromebook || !midIsDeXMode || !midManualBackButton) {
384 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
387 fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
389 if (!fidSeparateMouseAndTouch) {
390 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
396 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
397 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
399 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
401 Android_JNI_SetupThread();
403 mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
405 midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
406 "audioOpen", "(IIII)[I");
407 midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
408 "audioWriteByteBuffer", "([B)V");
409 midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
410 "audioWriteShortBuffer", "([S)V");
411 midAudioWriteFloatBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
412 "audioWriteFloatBuffer", "([F)V");
413 midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
414 "audioClose", "()V");
415 midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
416 "captureOpen", "(IIII)[I");
417 midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
418 "captureReadByteBuffer", "([BZ)I");
419 midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
420 "captureReadShortBuffer", "([SZ)I");
421 midCaptureReadFloatBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
422 "captureReadFloatBuffer", "([FZ)I");
423 midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
424 "captureClose", "()V");
426 if (!midAudioOpen || !midAudioWriteByteBuffer || !midAudioWriteShortBuffer || !midAudioWriteFloatBuffer || !midAudioClose ||
427 !midCaptureOpen || !midCaptureReadByteBuffer || !midCaptureReadShortBuffer || !midCaptureReadFloatBuffer || !midCaptureClose) {
428 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
434 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
435 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
437 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
439 Android_JNI_SetupThread();
441 mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
443 midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
444 "pollInputDevices", "()V");
445 midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
446 "pollHapticDevices", "()V");
447 midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
448 "hapticRun", "(IFI)V");
449 midHapticStop = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
450 "hapticStop", "(I)V");
452 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticStop) {
453 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
459 /* SDL main function prototype */
460 typedef int (*SDL_main_func)(int argc, char *argv[]);
462 /* Start up the SDL app */
463 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
466 const char *library_file;
467 void *library_handle;
469 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
471 library_file = (*env)->GetStringUTFChars(env, library, NULL);
472 library_handle = dlopen(library_file, RTLD_GLOBAL);
473 if (library_handle) {
474 const char *function_name;
475 SDL_main_func SDL_main;
477 function_name = (*env)->GetStringUTFChars(env, function, NULL);
478 SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
486 /* Prepare the arguments. */
487 len = (*env)->GetArrayLength(env, array);
488 argv = SDL_small_alloc(char*, 1 + len + 1, &isstack); /* !!! FIXME: check for NULL */
490 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
491 https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
493 argv[argc++] = SDL_strdup("app_process");
494 for (i = 0; i < len; ++i) {
497 jstring string = (*env)->GetObjectArrayElement(env, array, i);
499 utf = (*env)->GetStringUTFChars(env, string, 0);
501 arg = SDL_strdup(utf);
502 (*env)->ReleaseStringUTFChars(env, string, utf);
504 (*env)->DeleteLocalRef(env, string);
507 arg = SDL_strdup("");
514 /* Run the application. */
515 status = SDL_main(argc, argv);
517 /* Release the arguments. */
518 for (i = 0; i < argc; ++i) {
521 SDL_small_free(argv, isstack);
524 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
526 (*env)->ReleaseStringUTFChars(env, function, function_name);
528 dlclose(library_handle);
531 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
533 (*env)->ReleaseStringUTFChars(env, library, library_file);
535 /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
542 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
543 JNIEnv* env, jclass jcls,
546 const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
547 SDL_SendDropFile(NULL, path);
548 (*env)->ReleaseStringUTFChars(env, filename, path);
549 SDL_SendDropComplete(NULL);
553 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
554 JNIEnv* env, jclass jcls,
555 jint surfaceWidth, jint surfaceHeight,
556 jint deviceWidth, jint deviceHeight, jint format, jfloat rate)
558 Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, format, rate);
561 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeOrientationChanged)(
562 JNIEnv *env, jclass jcls,
565 SDL_VideoDisplay *display = SDL_GetDisplay(0);
566 SDL_SendDisplayEvent(display, SDL_DISPLAYEVENT_ORIENTATION, orientation);
570 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
571 JNIEnv* env, jclass jcls,
572 jint device_id, jint keycode)
574 return Android_OnPadDown(device_id, keycode);
578 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
579 JNIEnv* env, jclass jcls,
580 jint device_id, jint keycode)
582 return Android_OnPadUp(device_id, keycode);
586 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
587 JNIEnv* env, jclass jcls,
588 jint device_id, jint axis, jfloat value)
590 Android_OnJoy(device_id, axis, value);
594 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
595 JNIEnv* env, jclass jcls,
596 jint device_id, jint hat_id, jint x, jint y)
598 Android_OnHat(device_id, hat_id, x, y);
602 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
603 JNIEnv* env, jclass jcls,
604 jint device_id, jstring device_name, jstring device_desc,
605 jint vendor_id, jint product_id, jboolean is_accelerometer,
606 jint button_mask, jint naxes, jint nhats, jint nballs)
609 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
610 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
612 retval = Android_AddJoystick(device_id, name, desc, vendor_id, product_id, is_accelerometer ? SDL_TRUE : SDL_FALSE, button_mask, naxes, nhats, nballs);
614 (*env)->ReleaseStringUTFChars(env, device_name, name);
615 (*env)->ReleaseStringUTFChars(env, device_desc, desc);
620 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
621 JNIEnv* env, jclass jcls,
624 return Android_RemoveJoystick(device_id);
627 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
628 JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
631 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
633 retval = Android_AddHaptic(device_id, name);
635 (*env)->ReleaseStringUTFChars(env, device_name, name);
640 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
641 JNIEnv* env, jclass jcls, jint device_id)
643 return Android_RemoveHaptic(device_id);
647 /* Surface Created */
648 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
650 SDL_WindowData *data;
651 SDL_VideoDevice *_this;
653 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
657 _this = SDL_GetVideoDevice();
658 data = (SDL_WindowData *) Android_Window->driverdata;
660 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
661 if (data->egl_surface == EGL_NO_SURFACE) {
662 if(data->native_window) {
663 ANativeWindow_release(data->native_window);
665 data->native_window = Android_JNI_GetNativeWindow();
666 data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
669 /* GL Context handling is done in the event loop because this function is run from the Java thread */
673 /* Surface Destroyed */
674 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls)
676 /* We have to clear the current context and destroy the egl surface here
677 * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
678 * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
680 SDL_WindowData *data;
681 SDL_VideoDevice *_this;
683 if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
687 _this = SDL_GetVideoDevice();
688 data = (SDL_WindowData *) Android_Window->driverdata;
690 if (data->egl_surface != EGL_NO_SURFACE) {
691 SDL_EGL_MakeCurrent(_this, NULL, NULL);
692 SDL_EGL_DestroySurface(_this, data->egl_surface);
693 data->egl_surface = EGL_NO_SURFACE;
696 /* GL Context handling is done in the event loop because this function is run from the Java thread */
701 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
702 JNIEnv* env, jclass jcls,
705 Android_OnKeyDown(keycode);
709 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
710 JNIEnv* env, jclass jcls,
713 Android_OnKeyUp(keycode);
716 /* Keyboard Focus Lost */
717 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
718 JNIEnv* env, jclass jcls)
720 /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
726 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
727 JNIEnv* env, jclass jcls,
728 jint touch_device_id_in, jint pointer_finger_id_in,
729 jint action, jfloat x, jfloat y, jfloat p)
731 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
735 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
736 JNIEnv* env, jclass jcls,
737 jint button, jint action, jfloat x, jfloat y, jboolean relative)
739 Android_OnMouse(button, action, x, y, relative);
743 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
744 JNIEnv* env, jclass jcls,
745 jfloat x, jfloat y, jfloat z)
747 fLastAccelerometer[0] = x;
748 fLastAccelerometer[1] = y;
749 fLastAccelerometer[2] = z;
750 bHasNewData = SDL_TRUE;
754 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
755 JNIEnv* env, jclass jcls)
757 SDL_SendClipboardUpdate();
761 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
762 JNIEnv* env, jclass cls)
764 SDL_SendAppEvent(SDL_APP_LOWMEMORY);
768 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
769 JNIEnv* env, jclass cls)
771 /* Discard previous events. The user should have handled state storage
772 * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
773 * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
774 SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
775 /* Inject a SDL_QUIT event */
777 SDL_SendAppEvent(SDL_APP_TERMINATING);
778 /* Resume the event loop so that the app can catch SDL_QUIT which
779 * should now be the top event in the event queue. */
780 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
784 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
785 JNIEnv* env, jclass cls)
787 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
789 if (Android_Window) {
790 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
791 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
792 SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
793 SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
795 /* *After* sending the relevant events, signal the pause semaphore
796 * so the event loop knows to pause and (optionally) block itself */
797 if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
802 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
803 JNIEnv* env, jclass cls)
805 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
807 if (Android_Window) {
808 SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
809 SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
810 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
811 SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
812 /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
813 * We can't restore the GL Context here because it needs to be done on the SDL main thread
814 * and this function will be called from the Java thread instead.
816 if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
820 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
821 JNIEnv* env, jclass cls,
822 jstring text, jint newCursorPosition)
824 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
826 SDL_SendKeyboardText(utftext);
828 (*env)->ReleaseStringUTFChars(env, text, utftext);
831 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
832 JNIEnv* env, jclass cls,
835 SDL_Scancode code = SDL_SCANCODE_UNKNOWN;
838 // We do not care about bigger than 127.
839 if (chUnicode < 127) {
840 AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode];
845 if (mod & KMOD_SHIFT) {
846 /* If character uses shift, press shift down */
847 SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
850 /* send a keydown and keyup even for the character */
851 SDL_SendKeyboardKey(SDL_PRESSED, code);
852 SDL_SendKeyboardKey(SDL_RELEASED, code);
854 if (mod & KMOD_SHIFT) {
855 /* If character uses shift, press shift back up */
856 SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
861 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
862 JNIEnv* env, jclass cls,
863 jstring text, jint newCursorPosition)
865 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
867 SDL_SendEditingText(utftext, 0, 0);
869 (*env)->ReleaseStringUTFChars(env, text, utftext);
872 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
873 JNIEnv* env, jclass cls,
876 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
877 const char *hint = SDL_GetHint(utfname);
879 jstring result = (*env)->NewStringUTF(env, hint);
880 (*env)->ReleaseStringUTFChars(env, name, utfname);
885 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
886 JNIEnv* env, jclass cls,
887 jstring name, jstring value)
889 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
890 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
892 SDL_setenv(utfname, utfvalue, 1);
894 (*env)->ReleaseStringUTFChars(env, name, utfname);
895 (*env)->ReleaseStringUTFChars(env, value, utfvalue);
899 /*******************************************************************************
900 Functions called by SDL into Java
901 *******************************************************************************/
903 static int s_active = 0;
904 struct LocalReferenceHolder
910 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
912 struct LocalReferenceHolder refholder;
913 refholder.m_env = NULL;
914 refholder.m_func = func;
916 SDL_Log("Entering function %s", func);
921 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
923 const int capacity = 16;
924 if ((*env)->PushLocalFrame(env, capacity) < 0) {
925 SDL_SetError("Failed to allocate enough JVM local references");
929 refholder->m_env = env;
933 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
936 SDL_Log("Leaving function %s", refholder->m_func);
938 if (refholder->m_env) {
939 JNIEnv* env = refholder->m_env;
940 (*env)->PopLocalFrame(env, NULL);
945 static SDL_bool LocalReferenceHolder_IsActive(void)
950 ANativeWindow* Android_JNI_GetNativeWindow(void)
954 JNIEnv *env = Android_JNI_GetEnv();
956 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
957 anw = ANativeWindow_fromSurface(env, s);
958 (*env)->DeleteLocalRef(env, s);
963 void Android_JNI_SetActivityTitle(const char *title)
965 JNIEnv *mEnv = Android_JNI_GetEnv();
967 jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
968 (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle);
969 (*mEnv)->DeleteLocalRef(mEnv, jtitle);
972 void Android_JNI_SetWindowStyle(SDL_bool fullscreen)
974 JNIEnv *mEnv = Android_JNI_GetEnv();
975 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
978 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
980 JNIEnv *mEnv = Android_JNI_GetEnv();
982 jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
983 (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
984 (*mEnv)->DeleteLocalRef(mEnv, jhint);
987 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
990 SDL_bool retval = SDL_FALSE;
993 for (i = 0; i < 3; ++i) {
994 values[i] = fLastAccelerometer[i];
996 bHasNewData = SDL_FALSE;
1003 static void Android_JNI_ThreadDestroyed(void* value)
1005 /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
1006 JNIEnv *env = (JNIEnv*) value;
1008 (*mJavaVM)->DetachCurrentThread(mJavaVM);
1009 pthread_setspecific(mThreadKey, NULL);
1013 JNIEnv* Android_JNI_GetEnv(void)
1015 /* From http://developer.android.com/guide/practices/jni.html
1016 * All threads are Linux threads, scheduled by the kernel.
1017 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
1018 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
1019 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
1020 * and cannot make JNI calls.
1021 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
1022 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
1024 * Note: You can call this function any number of times for the same thread, there's no harm in it
1028 int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
1030 LOGE("failed to attach current thread");
1034 /* From http://developer.android.com/guide/practices/jni.html
1035 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
1036 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
1037 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
1038 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
1039 * Note: The destructor is not called unless the stored value is != NULL
1040 * Note: You can call this function any number of times for the same thread, there's no harm in it
1041 * (except for some lost CPU cycles)
1043 pthread_setspecific(mThreadKey, (void*) env);
1048 int Android_JNI_SetupThread(void)
1050 Android_JNI_GetEnv();
1057 static int audioBufferFormat = 0;
1058 static jobject audioBuffer = NULL;
1059 static void* audioBufferPinned = NULL;
1060 static int captureBufferFormat = 0;
1061 static jobject captureBuffer = NULL;
1063 int Android_JNI_OpenAudioDevice(int iscapture, SDL_AudioSpec *spec)
1066 int numBufferFrames;
1067 jobject jbufobj = NULL;
1069 int *resultElements;
1072 JNIEnv *env = Android_JNI_GetEnv();
1075 LOGE("callback_handler: failed to attach current thread");
1077 Android_JNI_SetupThread();
1079 switch (spec->format) {
1081 audioformat = ENCODING_PCM_8BIT;
1084 audioformat = ENCODING_PCM_16BIT;
1087 audioformat = ENCODING_PCM_FLOAT;
1090 return SDL_SetError("Unsupported audio format: 0x%x", spec->format);
1094 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
1095 result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midCaptureOpen, spec->freq, audioformat, spec->channels, spec->samples);
1097 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
1098 result = (*env)->CallStaticObjectMethod(env, mAudioManagerClass, midAudioOpen, spec->freq, audioformat, spec->channels, spec->samples);
1100 if (result == NULL) {
1101 /* Error during audio initialization, error printed from Java */
1102 return SDL_SetError("Java-side initialization failed");
1105 if ((*env)->GetArrayLength(env, (jintArray)result) != 4) {
1106 return SDL_SetError("Unexpected results from Java, expected 4, got %d", (*env)->GetArrayLength(env, (jintArray)result));
1109 resultElements = (*env)->GetIntArrayElements(env, (jintArray)result, &isCopy);
1110 spec->freq = resultElements[0];
1111 audioformat = resultElements[1];
1112 switch (audioformat) {
1113 case ENCODING_PCM_8BIT:
1114 spec->format = AUDIO_U8;
1116 case ENCODING_PCM_16BIT:
1117 spec->format = AUDIO_S16;
1119 case ENCODING_PCM_FLOAT:
1120 spec->format = AUDIO_F32;
1123 return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
1125 spec->channels = resultElements[2];
1126 spec->samples = resultElements[3];
1127 (*env)->ReleaseIntArrayElements(env, (jintArray)result, resultElements, JNI_ABORT);
1128 (*env)->DeleteLocalRef(env, result);
1130 /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
1131 * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
1132 switch (audioformat) {
1133 case ENCODING_PCM_8BIT:
1135 jbyteArray audioBufferLocal = (*env)->NewByteArray(env, spec->samples * spec->channels);
1136 if (audioBufferLocal) {
1137 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1138 (*env)->DeleteLocalRef(env, audioBufferLocal);
1142 case ENCODING_PCM_16BIT:
1144 jshortArray audioBufferLocal = (*env)->NewShortArray(env, spec->samples * spec->channels);
1145 if (audioBufferLocal) {
1146 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1147 (*env)->DeleteLocalRef(env, audioBufferLocal);
1151 case ENCODING_PCM_FLOAT:
1153 jfloatArray audioBufferLocal = (*env)->NewFloatArray(env, spec->samples * spec->channels);
1154 if (audioBufferLocal) {
1155 jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1156 (*env)->DeleteLocalRef(env, audioBufferLocal);
1161 return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
1164 if (jbufobj == NULL) {
1165 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer");
1166 return SDL_OutOfMemory();
1170 captureBufferFormat = audioformat;
1171 captureBuffer = jbufobj;
1173 audioBufferFormat = audioformat;
1174 audioBuffer = jbufobj;
1176 numBufferFrames = (*env)->GetArrayLength(env, (jarray)jbufobj);
1181 switch (audioformat) {
1182 case ENCODING_PCM_8BIT:
1183 audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
1185 case ENCODING_PCM_16BIT:
1186 audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
1188 case ENCODING_PCM_FLOAT:
1189 audioBufferPinned = (*env)->GetFloatArrayElements(env, (jfloatArray)audioBuffer, &isCopy);
1192 return SDL_SetError("Unexpected audio format from Java: %d\n", audioformat);
1198 int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi)
1200 JNIEnv *env = Android_JNI_GetEnv();
1202 jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI);
1203 jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj);
1205 jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F");
1206 jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F");
1207 jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I");
1209 float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi);
1210 float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi);
1211 int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi);
1214 (*env)->DeleteLocalRef(env, jDisplayObj);
1215 (*env)->DeleteLocalRef(env, jDisplayClass);
1218 *ddpi = (float)nativeDdpi;
1230 void * Android_JNI_GetAudioBuffer(void)
1232 return audioBufferPinned;
1235 void Android_JNI_WriteAudioBuffer(void)
1237 JNIEnv *mAudioEnv = Android_JNI_GetEnv();
1239 switch (audioBufferFormat) {
1240 case ENCODING_PCM_8BIT:
1241 (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
1242 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
1244 case ENCODING_PCM_16BIT:
1245 (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
1246 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
1248 case ENCODING_PCM_FLOAT:
1249 (*mAudioEnv)->ReleaseFloatArrayElements(mAudioEnv, (jfloatArray)audioBuffer, (jfloat *)audioBufferPinned, JNI_COMMIT);
1250 (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteFloatBuffer, (jfloatArray)audioBuffer);
1253 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: unhandled audio buffer format");
1257 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
1260 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
1262 JNIEnv *env = Android_JNI_GetEnv();
1263 jboolean isCopy = JNI_FALSE;
1266 switch (captureBufferFormat) {
1267 case ENCODING_PCM_8BIT:
1268 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
1269 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
1271 jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
1272 SDL_memcpy(buffer, ptr, br);
1273 (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
1276 case ENCODING_PCM_16BIT:
1277 SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / sizeof(Sint16)));
1278 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
1280 jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
1281 br *= sizeof(Sint16);
1282 SDL_memcpy(buffer, ptr, br);
1283 (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
1286 case ENCODING_PCM_FLOAT:
1287 SDL_assert((*env)->GetArrayLength(env, (jfloatArray)captureBuffer) == (buflen / sizeof(float)));
1288 br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_TRUE);
1290 jfloat *ptr = (*env)->GetFloatArrayElements(env, (jfloatArray)captureBuffer, &isCopy);
1291 br *= sizeof(float);
1292 SDL_memcpy(buffer, ptr, br);
1293 (*env)->ReleaseFloatArrayElements(env, (jfloatArray)captureBuffer, (jfloat *)ptr, JNI_ABORT);
1297 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: unhandled capture buffer format");
1303 void Android_JNI_FlushCapturedAudio(void)
1305 JNIEnv *env = Android_JNI_GetEnv();
1306 #if 0 /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
1307 switch (captureBufferFormat) {
1308 case ENCODING_PCM_8BIT:
1310 const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
1311 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1314 case ENCODING_PCM_16BIT:
1316 const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
1317 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1320 case ENCODING_PCM_FLOAT:
1322 const jint len = (*env)->GetArrayLength(env, (jfloatArray)captureBuffer);
1323 while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1327 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: flushing unhandled capture buffer format");
1331 switch (captureBufferFormat) {
1332 case ENCODING_PCM_8BIT:
1333 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
1335 case ENCODING_PCM_16BIT:
1336 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
1338 case ENCODING_PCM_FLOAT:
1339 (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadFloatBuffer, (jfloatArray)captureBuffer, JNI_FALSE);
1342 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: flushing unhandled capture buffer format");
1348 void Android_JNI_CloseAudioDevice(const int iscapture)
1350 JNIEnv *env = Android_JNI_GetEnv();
1353 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
1354 if (captureBuffer) {
1355 (*env)->DeleteGlobalRef(env, captureBuffer);
1356 captureBuffer = NULL;
1359 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
1361 (*env)->DeleteGlobalRef(env, audioBuffer);
1363 audioBufferPinned = NULL;
1368 /* Test for an exception and call SDL_SetError with its detail if one occurs */
1369 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
1370 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
1372 JNIEnv *mEnv = Android_JNI_GetEnv();
1373 jthrowable exception;
1375 SDL_assert(LocalReferenceHolder_IsActive());
1377 exception = (*mEnv)->ExceptionOccurred(mEnv);
1378 if (exception != NULL) {
1381 /* Until this happens most JNI operations have undefined behaviour */
1382 (*mEnv)->ExceptionClear(mEnv);
1385 jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
1386 jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
1387 jstring exceptionName;
1388 const char* exceptionNameUTF8;
1389 jstring exceptionMessage;
1391 mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
1392 exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
1393 exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
1395 mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
1396 exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
1398 if (exceptionMessage != NULL) {
1399 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
1400 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
1401 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
1403 SDL_SetError("%s", exceptionNameUTF8);
1406 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
1415 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
1417 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1423 jobject assetManager;
1424 jobject inputStream;
1426 jobject readableByteChannel;
1427 jstring fileNameJString;
1430 jfieldID descriptor;
1432 JNIEnv *mEnv = Android_JNI_GetEnv();
1433 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1437 fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
1438 ctx->hidden.androidio.position = 0;
1440 /* context = SDLActivity.getContext(); */
1441 context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext);
1443 /* assetManager = context.getAssets(); */
1444 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
1445 "getAssets", "()Landroid/content/res/AssetManager;");
1446 assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
1448 /* First let's try opening the file to obtain an AssetFileDescriptor.
1449 * This method reads the files directly from the APKs using standard *nix calls
1451 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
1452 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
1453 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1457 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
1458 ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1459 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1463 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
1464 ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1465 if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1469 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
1470 fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
1471 fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
1472 descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
1473 ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
1474 ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1476 /* Seek to the correct offset in the file. */
1477 lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
1481 /* Disabled log message because of spam on the Nexus 7 */
1482 /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
1484 /* Try the old method using InputStream */
1485 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1487 /* inputStream = assetManager.open(<filename>); */
1488 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
1489 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
1490 inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
1491 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1492 /* Try fallback to APK expansion files */
1493 inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
1495 /* Exception is checked first because it always needs to be cleared.
1496 * If no exception occurred then the last SDL error message is kept.
1498 if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
1503 ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1505 /* Despite all the visible documentation on [Asset]InputStream claiming
1506 * that the .available() method is not guaranteed to return the entire file
1507 * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
1508 * android/apis/content/ReadAsset.java imply that Android's
1509 * AssetInputStream.available() /will/ always return the total file size
1512 /* size = inputStream.available(); */
1513 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1514 "available", "()I");
1515 ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
1516 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1520 /* readableByteChannel = Channels.newChannel(inputStream); */
1521 channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
1522 mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
1524 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
1525 readableByteChannel = (*mEnv)->CallStaticObjectMethod(
1526 mEnv, channels, mid, inputStream);
1527 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1531 ctx->hidden.androidio.readableByteChannelRef =
1532 (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
1534 /* Store .read id for reading purposes */
1535 mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
1536 "read", "(Ljava/nio/ByteBuffer;)I");
1537 ctx->hidden.androidio.readMethod = mid;
1544 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1546 if(ctx->hidden.androidio.inputStreamRef != NULL) {
1547 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1550 if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
1551 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1554 if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
1555 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1560 LocalReferenceHolder_Cleanup(&refs);
1564 int Android_JNI_FileOpen(SDL_RWops* ctx,
1565 const char* fileName, const char* mode)
1567 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1568 JNIEnv *mEnv = Android_JNI_GetEnv();
1570 jstring fileNameJString;
1572 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1573 LocalReferenceHolder_Cleanup(&refs);
1578 LocalReferenceHolder_Cleanup(&refs);
1582 fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
1583 ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
1584 ctx->hidden.androidio.inputStreamRef = NULL;
1585 ctx->hidden.androidio.readableByteChannelRef = NULL;
1586 ctx->hidden.androidio.readMethod = NULL;
1587 ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1589 retval = Internal_Android_JNI_FileOpen(ctx);
1590 LocalReferenceHolder_Cleanup(&refs);
1594 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
1595 size_t size, size_t maxnum)
1597 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1599 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1600 size_t bytesMax = size * maxnum;
1602 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
1603 bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
1605 result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
1607 ctx->hidden.androidio.position += result;
1608 LocalReferenceHolder_Cleanup(&refs);
1609 return result / size;
1611 LocalReferenceHolder_Cleanup(&refs);
1614 jlong bytesRemaining = (jlong) (size * maxnum);
1615 jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
1618 jobject readableByteChannel;
1619 jmethodID readMethod;
1622 /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
1623 if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
1625 mEnv = Android_JNI_GetEnv();
1626 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1627 LocalReferenceHolder_Cleanup(&refs);
1631 readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
1632 readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
1633 byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
1635 while (bytesRemaining > 0) {
1636 /* result = readableByteChannel.read(...); */
1637 int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
1639 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1640 LocalReferenceHolder_Cleanup(&refs);
1648 bytesRemaining -= result;
1649 bytesRead += result;
1650 ctx->hidden.androidio.position += result;
1652 LocalReferenceHolder_Cleanup(&refs);
1653 return bytesRead / size;
1657 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
1658 size_t size, size_t num)
1660 SDL_SetError("Cannot write to Android package filesystem");
1664 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
1666 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1669 JNIEnv *mEnv = Android_JNI_GetEnv();
1671 if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1672 LocalReferenceHolder_Cleanup(&refs);
1673 return SDL_SetError("Failed to allocate enough JVM local references");
1678 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1681 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1682 jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
1683 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1685 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1686 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1687 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1692 jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
1694 /* inputStream.close(); */
1695 jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1697 (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1698 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1699 (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1700 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1710 LocalReferenceHolder_Cleanup(&refs);
1715 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
1717 return ctx->hidden.androidio.size;
1720 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
1722 if (ctx->hidden.androidio.assetFileDescriptorRef) {
1726 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1727 offset += ctx->hidden.androidio.offset;
1730 offset += ctx->hidden.androidio.position;
1731 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1732 offset += ctx->hidden.androidio.offset;
1735 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
1738 return SDL_SetError("Unknown value for 'whence'");
1742 ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
1743 if (ret == -1) return -1;
1744 ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
1751 newPosition = offset;
1754 newPosition = ctx->hidden.androidio.position + offset;
1757 newPosition = ctx->hidden.androidio.size + offset;
1760 return SDL_SetError("Unknown value for 'whence'");
1763 /* Validate the new position */
1764 if (newPosition < 0) {
1765 return SDL_Error(SDL_EFSEEK);
1767 if (newPosition > ctx->hidden.androidio.size) {
1768 newPosition = ctx->hidden.androidio.size;
1771 movement = newPosition - ctx->hidden.androidio.position;
1773 unsigned char buffer[4096];
1775 /* The easy case where we're seeking forwards */
1776 while (movement > 0) {
1777 Sint64 amount = sizeof (buffer);
1779 if (amount > movement) {
1782 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
1784 /* Failed to read/skip the required amount, so fail */
1791 } else if (movement < 0) {
1792 /* We can't seek backwards so we have to reopen the file and seek */
1793 /* forwards which obviously isn't very efficient */
1794 Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
1795 Internal_Android_JNI_FileOpen(ctx);
1796 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1800 return ctx->hidden.androidio.position;
1804 int Android_JNI_FileClose(SDL_RWops* ctx)
1806 return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
1809 int Android_JNI_SetClipboardText(const char* text)
1811 JNIEnv* env = Android_JNI_GetEnv();
1812 jstring string = (*env)->NewStringUTF(env, text);
1813 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
1814 (*env)->DeleteLocalRef(env, string);
1818 char* Android_JNI_GetClipboardText(void)
1820 JNIEnv* env = Android_JNI_GetEnv();
1824 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
1826 const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1828 text = SDL_strdup(utf);
1829 (*env)->ReleaseStringUTFChars(env, string, utf);
1831 (*env)->DeleteLocalRef(env, string);
1834 return (text == NULL) ? SDL_strdup("") : text;
1837 SDL_bool Android_JNI_HasClipboardText(void)
1839 JNIEnv* env = Android_JNI_GetEnv();
1840 jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
1841 return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
1844 /* returns 0 on success or -1 on error (others undefined then)
1845 * returns truthy or falsy value in plugged, charged and battery
1846 * returns the value in seconds and percent or -1 if not available
1848 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1850 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1851 JNIEnv* env = Android_JNI_GetEnv();
1862 if (!LocalReferenceHolder_Init(&refs, env)) {
1863 LocalReferenceHolder_Cleanup(&refs);
1868 /* context = SDLActivity.getContext(); */
1869 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1871 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1873 cls = (*env)->FindClass(env, "android/content/IntentFilter");
1875 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1876 filter = (*env)->NewObject(env, cls, mid, action);
1878 (*env)->DeleteLocalRef(env, action);
1880 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1881 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1883 (*env)->DeleteLocalRef(env, filter);
1885 cls = (*env)->GetObjectClass(env, intent);
1887 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1889 /* Watch out for C89 scoping rules because of the macro */
1890 #define GET_INT_EXTRA(var, key) \
1892 iname = (*env)->NewStringUTF(env, key); \
1893 var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1894 (*env)->DeleteLocalRef(env, iname);
1896 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1898 /* Watch out for C89 scoping rules because of the macro */
1899 #define GET_BOOL_EXTRA(var, key) \
1901 bname = (*env)->NewStringUTF(env, key); \
1902 var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1903 (*env)->DeleteLocalRef(env, bname);
1906 /* Watch out for C89 scoping rules because of the macro */
1907 GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1909 LocalReferenceHolder_Cleanup(&refs);
1912 /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1913 /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1914 *plugged = (0 < plug) ? 1 : 0;
1918 /* Watch out for C89 scoping rules because of the macro */
1919 GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1921 LocalReferenceHolder_Cleanup(&refs);
1924 /* 5 == BatteryManager.BATTERY_STATUS_FULL */
1925 *charged = (status == 5) ? 1 : 0;
1929 GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1930 *battery = present ? 1 : 0;
1934 *seconds = -1; /* not possible */
1941 /* Watch out for C89 scoping rules because of the macro */
1943 GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1946 /* Watch out for C89 scoping rules because of the macro */
1948 GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1952 if ((level == -1) || (scale == -1)) {
1953 LocalReferenceHolder_Cleanup(&refs);
1956 *percent = level * 100 / scale;
1959 (*env)->DeleteLocalRef(env, intent);
1961 LocalReferenceHolder_Cleanup(&refs);
1965 /* returns number of found touch devices as return value and ids in parameter ids */
1966 int Android_JNI_GetTouchDeviceIds(int **ids) {
1967 JNIEnv *env = Android_JNI_GetEnv();
1968 jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1969 jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources);
1973 number = (int) (*env)->GetArrayLength(env, array);
1975 jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1978 *ids = SDL_malloc(number * sizeof (**ids));
1979 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1980 (*ids)[i] = elements[i];
1982 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1985 (*env)->DeleteLocalRef(env, array);
1990 /* sets the mSeparateMouseAndTouch field */
1991 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
1993 JNIEnv *env = Android_JNI_GetEnv();
1994 (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
1997 void Android_JNI_PollInputDevices(void)
1999 JNIEnv *env = Android_JNI_GetEnv();
2000 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
2003 void Android_JNI_PollHapticDevices(void)
2005 JNIEnv *env = Android_JNI_GetEnv();
2006 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
2009 void Android_JNI_HapticRun(int device_id, float intensity, int length)
2011 JNIEnv *env = Android_JNI_GetEnv();
2012 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length);
2015 void Android_JNI_HapticStop(int device_id)
2017 JNIEnv *env = Android_JNI_GetEnv();
2018 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id);
2021 /* See SDLActivity.java for constants. */
2022 #define COMMAND_SET_KEEP_SCREEN_ON 5
2024 /* sends message to be handled on the UI event dispatch thread */
2025 int Android_JNI_SendMessage(int command, int param)
2027 JNIEnv *env = Android_JNI_GetEnv();
2029 success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
2030 return success ? 0 : -1;
2033 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
2035 Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
2038 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
2040 JNIEnv *env = Android_JNI_GetEnv();
2041 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
2048 void Android_JNI_HideTextInput(void)
2050 /* has to match Activity constant */
2051 const int COMMAND_TEXTEDIT_HIDE = 3;
2052 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
2055 SDL_bool Android_JNI_IsScreenKeyboardShown()
2057 JNIEnv *mEnv = Android_JNI_GetEnv();
2058 jboolean is_shown = 0;
2059 is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown);
2064 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
2072 jintArray button_flags;
2073 jintArray button_ids;
2074 jobjectArray button_texts;
2080 env = Android_JNI_GetEnv();
2082 /* convert parameters */
2084 clazz = (*env)->FindClass(env, "java/lang/String");
2086 title = (*env)->NewStringUTF(env, messageboxdata->title);
2087 message = (*env)->NewStringUTF(env, messageboxdata->message);
2089 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
2090 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
2091 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
2093 for (i = 0; i < messageboxdata->numbuttons; ++i) {
2094 temp = messageboxdata->buttons[i].flags;
2095 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
2096 temp = messageboxdata->buttons[i].buttonid;
2097 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
2098 text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
2099 (*env)->SetObjectArrayElement(env, button_texts, i, text);
2100 (*env)->DeleteLocalRef(env, text);
2103 if (messageboxdata->colorScheme) {
2104 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
2105 for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
2106 temp = (0xFF << 24) |
2107 (messageboxdata->colorScheme->colors[i].r << 16) |
2108 (messageboxdata->colorScheme->colors[i].g << 8) |
2109 (messageboxdata->colorScheme->colors[i].b << 0);
2110 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
2116 (*env)->DeleteLocalRef(env, clazz);
2118 /* context = SDLActivity.getContext(); */
2119 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2121 clazz = (*env)->GetObjectClass(env, context);
2123 mid = (*env)->GetMethodID(env, clazz,
2124 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
2125 *buttonid = (*env)->CallIntMethod(env, context, mid,
2126 messageboxdata->flags,
2134 (*env)->DeleteLocalRef(env, context);
2135 (*env)->DeleteLocalRef(env, clazz);
2137 /* delete parameters */
2139 (*env)->DeleteLocalRef(env, title);
2140 (*env)->DeleteLocalRef(env, message);
2141 (*env)->DeleteLocalRef(env, button_flags);
2142 (*env)->DeleteLocalRef(env, button_ids);
2143 (*env)->DeleteLocalRef(env, button_texts);
2144 (*env)->DeleteLocalRef(env, colors);
2150 //////////////////////////////////////////////////////////////////////////////
2152 // Functions exposed to SDL applications in SDL_system.h
2153 //////////////////////////////////////////////////////////////////////////////
2156 void *SDL_AndroidGetJNIEnv(void)
2158 return Android_JNI_GetEnv();
2161 void *SDL_AndroidGetActivity(void)
2163 /* See SDL_system.h for caveats on using this function. */
2165 JNIEnv *env = Android_JNI_GetEnv();
2170 /* return SDLActivity.getContext(); */
2171 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2174 SDL_bool SDL_IsAndroidTablet(void)
2176 JNIEnv *env = Android_JNI_GetEnv();
2177 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet);
2180 SDL_bool SDL_IsAndroidTV(void)
2182 JNIEnv *env = Android_JNI_GetEnv();
2183 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
2186 SDL_bool SDL_IsChromebook(void)
2188 JNIEnv *env = Android_JNI_GetEnv();
2189 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsChromebook);
2192 SDL_bool SDL_IsDeXMode(void)
2194 JNIEnv *env = Android_JNI_GetEnv();
2195 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode);
2198 void SDL_AndroidBackButton(void)
2200 JNIEnv *env = Android_JNI_GetEnv();
2201 return (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton);
2204 const char * SDL_AndroidGetInternalStoragePath(void)
2206 static char *s_AndroidInternalFilesPath = NULL;
2208 if (!s_AndroidInternalFilesPath) {
2209 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2216 JNIEnv *env = Android_JNI_GetEnv();
2217 if (!LocalReferenceHolder_Init(&refs, env)) {
2218 LocalReferenceHolder_Cleanup(&refs);
2222 /* context = SDLActivity.getContext(); */
2223 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2225 SDL_SetError("Couldn't get Android context!");
2226 LocalReferenceHolder_Cleanup(&refs);
2230 /* fileObj = context.getFilesDir(); */
2231 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2232 "getFilesDir", "()Ljava/io/File;");
2233 fileObject = (*env)->CallObjectMethod(env, context, mid);
2235 SDL_SetError("Couldn't get internal directory");
2236 LocalReferenceHolder_Cleanup(&refs);
2240 /* path = fileObject.getCanonicalPath(); */
2241 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2242 "getCanonicalPath", "()Ljava/lang/String;");
2243 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2244 if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
2245 LocalReferenceHolder_Cleanup(&refs);
2249 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2250 s_AndroidInternalFilesPath = SDL_strdup(path);
2251 (*env)->ReleaseStringUTFChars(env, pathString, path);
2253 LocalReferenceHolder_Cleanup(&refs);
2255 return s_AndroidInternalFilesPath;
2258 int SDL_AndroidGetExternalStorageState(void)
2260 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2263 jstring stateString;
2267 JNIEnv *env = Android_JNI_GetEnv();
2268 if (!LocalReferenceHolder_Init(&refs, env)) {
2269 LocalReferenceHolder_Cleanup(&refs);
2273 cls = (*env)->FindClass(env, "android/os/Environment");
2274 mid = (*env)->GetStaticMethodID(env, cls,
2275 "getExternalStorageState", "()Ljava/lang/String;");
2276 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
2278 state = (*env)->GetStringUTFChars(env, stateString, NULL);
2280 /* Print an info message so people debugging know the storage state */
2281 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
2283 if (SDL_strcmp(state, "mounted") == 0) {
2284 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
2285 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
2286 } else if (SDL_strcmp(state, "mounted_ro") == 0) {
2287 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
2291 (*env)->ReleaseStringUTFChars(env, stateString, state);
2293 LocalReferenceHolder_Cleanup(&refs);
2297 const char * SDL_AndroidGetExternalStoragePath(void)
2299 static char *s_AndroidExternalFilesPath = NULL;
2301 if (!s_AndroidExternalFilesPath) {
2302 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2309 JNIEnv *env = Android_JNI_GetEnv();
2310 if (!LocalReferenceHolder_Init(&refs, env)) {
2311 LocalReferenceHolder_Cleanup(&refs);
2315 /* context = SDLActivity.getContext(); */
2316 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2318 /* fileObj = context.getExternalFilesDir(); */
2319 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2320 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
2321 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2323 SDL_SetError("Couldn't get external directory");
2324 LocalReferenceHolder_Cleanup(&refs);
2328 /* path = fileObject.getAbsolutePath(); */
2329 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2330 "getAbsolutePath", "()Ljava/lang/String;");
2331 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2333 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2334 s_AndroidExternalFilesPath = SDL_strdup(path);
2335 (*env)->ReleaseStringUTFChars(env, pathString, path);
2337 LocalReferenceHolder_Cleanup(&refs);
2339 return s_AndroidExternalFilesPath;
2342 void Android_JNI_GetManifestEnvironmentVariables(void)
2344 if (!mActivityClass || !midGetManifestEnvironmentVariables) {
2345 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
2349 if (!bHasEnvironmentVariables) {
2350 JNIEnv *env = Android_JNI_GetEnv();
2351 SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
2353 bHasEnvironmentVariables = SDL_TRUE;
2358 int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
2360 JNIEnv *mEnv = Android_JNI_GetEnv();
2361 int custom_cursor = 0;
2363 pixels = (*mEnv)->NewIntArray(mEnv, surface->w * surface->h);
2365 (*mEnv)->SetIntArrayRegion(mEnv, pixels, 0, surface->w * surface->h, (int *)surface->pixels);
2366 custom_cursor = (*mEnv)->CallStaticIntMethod(mEnv, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y);
2367 (*mEnv)->DeleteLocalRef(mEnv, pixels);
2371 return custom_cursor;
2375 SDL_bool Android_JNI_SetCustomCursor(int cursorID)
2377 JNIEnv *mEnv = Android_JNI_GetEnv();
2378 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetCustomCursor, cursorID);
2381 SDL_bool Android_JNI_SetSystemCursor(int cursorID)
2383 JNIEnv *mEnv = Android_JNI_GetEnv();
2384 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetSystemCursor, cursorID);
2387 SDL_bool Android_JNI_SupportsRelativeMouse()
2389 JNIEnv *mEnv = Android_JNI_GetEnv();
2390 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSupportsRelativeMouse);
2393 SDL_bool Android_JNI_SetRelativeMouseEnabled(SDL_bool enabled)
2395 JNIEnv *mEnv = Android_JNI_GetEnv();
2396 return (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
2400 #endif /* __ANDROID__ */
2402 /* vi: set ts=4 sw=4 expandtab: */