1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
4
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.
8
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:
12
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.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_PLATFORM_ANDROID
24
25#include "SDL_android.h"
26
27#include "../../events/SDL_events_c.h"
28#include "../../video/android/SDL_androidkeyboard.h"
29#include "../../video/android/SDL_androidmouse.h"
30#include "../../video/android/SDL_androidtouch.h"
31#include "../../video/android/SDL_androidpen.h"
32#include "../../video/android/SDL_androidvideo.h"
33#include "../../video/android/SDL_androidwindow.h"
34#include "../../joystick/android/SDL_sysjoystick_c.h"
35#include "../../haptic/android/SDL_syshaptic_c.h"
36#include "../../hidapi/android/hid.h"
37#include "../../SDL_hints_c.h"
38
39#include <android/log.h>
40#include <android/configuration.h>
41#include <android/asset_manager_jni.h>
42#include <sys/system_properties.h>
43#include <pthread.h>
44#include <sys/types.h>
45#include <unistd.h>
46#include <dlfcn.h>
47
48#define SDL_JAVA_PREFIX org_libsdl_app
49#define CONCAT1(prefix, class, function) CONCAT2(prefix, class, function)
50#define CONCAT2(prefix, class, function) Java_##prefix##_##class##_##function
51#define SDL_JAVA_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
52#define SDL_JAVA_AUDIO_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function)
53#define SDL_JAVA_CONTROLLER_INTERFACE(function) CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
54#define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function) CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
55
56// Audio encoding definitions
57#define ENCODING_PCM_8BIT 3
58#define ENCODING_PCM_16BIT 2
59#define ENCODING_PCM_FLOAT 4
60
61// Java class SDLActivity
62JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)(
63 JNIEnv *env, jclass cls);
64
65JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
66 JNIEnv *env, jclass cls);
67
68JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)(
69 JNIEnv *env, jclass cls);
70
71JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeCleanupMainThread)(
72 JNIEnv *env, jclass cls);
73
74JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
75 JNIEnv *env, jclass cls,
76 jstring library, jstring function, jobject array);
77
78JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
79 JNIEnv *env, jclass jcls,
80 jstring filename);
81
82JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)(
83 JNIEnv *env, jclass jcls,
84 jint surfaceWidth, jint surfaceHeight,
85 jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate);
86
87JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
88 JNIEnv *env, jclass cls);
89
90JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(
91 JNIEnv *env, jclass jcls);
92
93JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
94 JNIEnv *env, jclass jcls);
95
96JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
97 JNIEnv *env, jclass jcls);
98
99JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
100 JNIEnv *env, jclass jcls,
101 jint keycode);
102
103JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
104 JNIEnv *env, jclass jcls,
105 jint keycode);
106
107JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
108 JNIEnv *env, jclass jcls);
109
110JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
111 JNIEnv *env, jclass jcls);
112
113JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
114 JNIEnv *env, jclass jcls,
115 jint touch_device_id_in, jint pointer_finger_id_in,
116 jint action, jfloat x, jfloat y, jfloat p);
117
118JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
119 JNIEnv *env, jclass jcls,
120 jint button, jint action, jfloat x, jfloat y, jboolean relative);
121
122JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)(
123 JNIEnv *env, jclass jcls,
124 jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p);
125
126JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
127 JNIEnv *env, jclass jcls,
128 jfloat x, jfloat y, jfloat z);
129
130JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
131 JNIEnv *env, jclass jcls);
132
133JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
134 JNIEnv *env, jclass cls);
135
136JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
137 JNIEnv *env, jclass cls);
138
139JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
140 JNIEnv *env, jclass cls, jboolean enabled);
141
142JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
143 JNIEnv *env, jclass cls);
144
145JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
146 JNIEnv *env, jclass cls);
147
148JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
149 JNIEnv *env, jclass cls);
150
151JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
152 JNIEnv *env, jclass cls);
153
154JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
155 JNIEnv *env, jclass cls, jboolean hasFocus);
156
157JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
158 JNIEnv *env, jclass cls,
159 jstring name);
160
161JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeGetHintBoolean)(
162 JNIEnv *env, jclass cls,
163 jstring name, jboolean default_value);
164
165JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
166 JNIEnv *env, jclass cls,
167 jstring name, jstring value);
168
169JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)(
170 JNIEnv *env, jclass cls,
171 jint orientation);
172
173JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)(
174 JNIEnv *env, jclass cls,
175 jint rotation);
176
177JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
178 JNIEnv *env, jclass cls,
179 jint left, jint right, jint top, jint bottom);
180
181JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
182 JNIEnv *env, jclass cls,
183 jint touchId, jstring name);
184
185JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
186 JNIEnv *env, jclass cls,
187 jint requestCode, jboolean result);
188
189JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)(
190 JNIEnv *env, jclass jcls);
191
192JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)(
193 JNIEnv *env, jclass jcls);
194
195JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)(
196 JNIEnv *env, jclass jcls,
197 jint requestCode, jobjectArray fileList, jint filter);
198
199static JNINativeMethod SDLActivity_tab[] = {
200 { "nativeGetVersion", "()Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetVersion) },
201 { "nativeSetupJNI", "()I", SDL_JAVA_INTERFACE(nativeSetupJNI) },
202 { "nativeInitMainThread", "()V", SDL_JAVA_INTERFACE(nativeInitMainThread) },
203 { "nativeCleanupMainThread", "()V", SDL_JAVA_INTERFACE(nativeCleanupMainThread) },
204 { "nativeRunMain", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)I", SDL_JAVA_INTERFACE(nativeRunMain) },
205 { "onNativeDropFile", "(Ljava/lang/String;)V", SDL_JAVA_INTERFACE(onNativeDropFile) },
206 { "nativeSetScreenResolution", "(IIIIFF)V", SDL_JAVA_INTERFACE(nativeSetScreenResolution) },
207 { "onNativeResize", "()V", SDL_JAVA_INTERFACE(onNativeResize) },
208 { "onNativeSurfaceCreated", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceCreated) },
209 { "onNativeSurfaceChanged", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceChanged) },
210 { "onNativeSurfaceDestroyed", "()V", SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed) },
211 { "onNativeKeyDown", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyDown) },
212 { "onNativeKeyUp", "(I)V", SDL_JAVA_INTERFACE(onNativeKeyUp) },
213 { "onNativeSoftReturnKey", "()Z", SDL_JAVA_INTERFACE(onNativeSoftReturnKey) },
214 { "onNativeKeyboardFocusLost", "()V", SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost) },
215 { "onNativeTouch", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativeTouch) },
216 { "onNativeMouse", "(IIFFZ)V", SDL_JAVA_INTERFACE(onNativeMouse) },
217 { "onNativePen", "(IIIFFF)V", SDL_JAVA_INTERFACE(onNativePen) },
218 { "onNativeAccel", "(FFF)V", SDL_JAVA_INTERFACE(onNativeAccel) },
219 { "onNativeClipboardChanged", "()V", SDL_JAVA_INTERFACE(onNativeClipboardChanged) },
220 { "nativeLowMemory", "()V", SDL_JAVA_INTERFACE(nativeLowMemory) },
221 { "onNativeLocaleChanged", "()V", SDL_JAVA_INTERFACE(onNativeLocaleChanged) },
222 { "onNativeDarkModeChanged", "(Z)V", SDL_JAVA_INTERFACE(onNativeDarkModeChanged) },
223 { "nativeSendQuit", "()V", SDL_JAVA_INTERFACE(nativeSendQuit) },
224 { "nativeQuit", "()V", SDL_JAVA_INTERFACE(nativeQuit) },
225 { "nativePause", "()V", SDL_JAVA_INTERFACE(nativePause) },
226 { "nativeResume", "()V", SDL_JAVA_INTERFACE(nativeResume) },
227 { "nativeFocusChanged", "(Z)V", SDL_JAVA_INTERFACE(nativeFocusChanged) },
228 { "nativeGetHint", "(Ljava/lang/String;)Ljava/lang/String;", SDL_JAVA_INTERFACE(nativeGetHint) },
229 { "nativeGetHintBoolean", "(Ljava/lang/String;Z)Z", SDL_JAVA_INTERFACE(nativeGetHintBoolean) },
230 { "nativeSetenv", "(Ljava/lang/String;Ljava/lang/String;)V", SDL_JAVA_INTERFACE(nativeSetenv) },
231 { "nativeSetNaturalOrientation", "(I)V", SDL_JAVA_INTERFACE(nativeSetNaturalOrientation) },
232 { "onNativeRotationChanged", "(I)V", SDL_JAVA_INTERFACE(onNativeRotationChanged) },
233 { "onNativeInsetsChanged", "(IIII)V", SDL_JAVA_INTERFACE(onNativeInsetsChanged) },
234 { "nativeAddTouch", "(ILjava/lang/String;)V", SDL_JAVA_INTERFACE(nativeAddTouch) },
235 { "nativePermissionResult", "(IZ)V", SDL_JAVA_INTERFACE(nativePermissionResult) },
236 { "nativeAllowRecreateActivity", "()Z", SDL_JAVA_INTERFACE(nativeAllowRecreateActivity) },
237 { "nativeCheckSDLThreadCounter", "()I", SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter) },
238 { "onNativeFileDialog", "(I[Ljava/lang/String;I)V", SDL_JAVA_INTERFACE(onNativeFileDialog) }
239};
240
241// Java class SDLInputConnection
242JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
243 JNIEnv *env, jclass cls,
244 jstring text, jint newCursorPosition);
245
246JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
247 JNIEnv *env, jclass cls,
248 jchar chUnicode);
249
250static JNINativeMethod SDLInputConnection_tab[] = {
251 { "nativeCommitText", "(Ljava/lang/String;I)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText) },
252 { "nativeGenerateScancodeForUnichar", "(C)V", SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar) }
253};
254
255// Java class SDLAudioManager
256JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
257 JNIEnv *env, jclass jcls);
258
259JNIEXPORT void JNICALL
260 SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording, jstring name,
261 jint device_id);
262
263JNIEXPORT void JNICALL
264 SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
265 jint device_id);
266
267static JNINativeMethod SDLAudioManager_tab[] = {
268 { "nativeSetupJNI", "()I", SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI) },
269 { "addAudioDevice", "(ZLjava/lang/String;I)V", SDL_JAVA_AUDIO_INTERFACE(addAudioDevice) },
270 { "removeAudioDevice", "(ZI)V", SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice) }
271};
272
273// Java class SDLControllerManager
274JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
275 JNIEnv *env, jclass jcls);
276
277JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
278 JNIEnv *env, jclass jcls,
279 jint device_id, jint keycode);
280
281JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
282 JNIEnv *env, jclass jcls,
283 jint device_id, jint keycode);
284
285JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
286 JNIEnv *env, jclass jcls,
287 jint device_id, jint axis, jfloat value);
288
289JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
290 JNIEnv *env, jclass jcls,
291 jint device_id, jint hat_id, jint x, jint y);
292
293JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
294 JNIEnv *env, jclass jcls,
295 jint device_id, jstring device_name, jstring device_desc, jint vendor_id, jint product_id,
296 jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble);
297
298JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
299 JNIEnv *env, jclass jcls,
300 jint device_id);
301
302JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
303 JNIEnv *env, jclass jcls,
304 jint device_id, jstring device_name);
305
306JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
307 JNIEnv *env, jclass jcls,
308 jint device_id);
309
310static JNINativeMethod SDLControllerManager_tab[] = {
311 { "nativeSetupJNI", "()I", SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI) },
312 { "onNativePadDown", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown) },
313 { "onNativePadUp", "(II)Z", SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp) },
314 { "onNativeJoy", "(IIF)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy) },
315 { "onNativeHat", "(IIII)V", SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat) },
316 { "nativeAddJoystick", "(ILjava/lang/String;Ljava/lang/String;IIIIIIZ)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick) },
317 { "nativeRemoveJoystick", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick) },
318 { "nativeAddHaptic", "(ILjava/lang/String;)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic) },
319 { "nativeRemoveHaptic", "(I)V", SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic) }
320};
321
322// Uncomment this to log messages entering and exiting methods in this file
323// #define DEBUG_JNI
324
325static void checkJNIReady(void);
326
327/*******************************************************************************
328 This file links the Java side of Android with libsdl
329*******************************************************************************/
330#include <jni.h>
331
332/*******************************************************************************
333 Globals
334*******************************************************************************/
335static pthread_key_t mThreadKey;
336static pthread_once_t key_once = PTHREAD_ONCE_INIT;
337static JavaVM *mJavaVM = NULL;
338
339// Main activity
340static jclass mActivityClass;
341
342// method signatures
343static jmethodID midClipboardGetText;
344static jmethodID midClipboardHasText;
345static jmethodID midClipboardSetText;
346static jmethodID midCreateCustomCursor;
347static jmethodID midDestroyCustomCursor;
348static jmethodID midGetContext;
349static jmethodID midGetManifestEnvironmentVariables;
350static jmethodID midGetNativeSurface;
351static jmethodID midInitTouch;
352static jmethodID midIsAndroidTV;
353static jmethodID midIsChromebook;
354static jmethodID midIsDeXMode;
355static jmethodID midIsScreenKeyboardShown;
356static jmethodID midIsTablet;
357static jmethodID midManualBackButton;
358static jmethodID midMinimizeWindow;
359static jmethodID midOpenURL;
360static jmethodID midRequestPermission;
361static jmethodID midShowToast;
362static jmethodID midSendMessage;
363static jmethodID midSetActivityTitle;
364static jmethodID midSetCustomCursor;
365static jmethodID midSetOrientation;
366static jmethodID midSetRelativeMouseEnabled;
367static jmethodID midSetSystemCursor;
368static jmethodID midSetWindowStyle;
369static jmethodID midShouldMinimizeOnFocusLoss;
370static jmethodID midShowTextInput;
371static jmethodID midSupportsRelativeMouse;
372static jmethodID midOpenFileDescriptor;
373static jmethodID midShowFileDialog;
374
375// audio manager
376static jclass mAudioManagerClass;
377
378// method signatures
379static jmethodID midRegisterAudioDeviceCallback;
380static jmethodID midUnregisterAudioDeviceCallback;
381static jmethodID midAudioSetThreadPriority;
382
383// controller manager
384static jclass mControllerManagerClass;
385
386// method signatures
387static jmethodID midPollInputDevices;
388static jmethodID midPollHapticDevices;
389static jmethodID midHapticRun;
390static jmethodID midHapticRumble;
391static jmethodID midHapticStop;
392
393// Accelerometer data storage
394static SDL_DisplayOrientation displayNaturalOrientation;
395static SDL_DisplayOrientation displayCurrentOrientation;
396static float fLastAccelerometer[3];
397static bool bHasNewData;
398
399static bool bHasEnvironmentVariables;
400
401// Android AssetManager
402static void Internal_Android_Create_AssetManager(void);
403static void Internal_Android_Destroy_AssetManager(void);
404static AAssetManager *asset_manager = NULL;
405static jobject javaAssetManagerRef = 0;
406
407// Re-create activity hint
408static SDL_AtomicInt bAllowRecreateActivity;
409
410static SDL_Mutex *Android_ActivityMutex = NULL;
411static SDL_Mutex *Android_LifecycleMutex = NULL;
412static SDL_Semaphore *Android_LifecycleEventSem = NULL;
413static SDL_AndroidLifecycleEvent Android_LifecycleEvents[SDL_NUM_ANDROID_LIFECYCLE_EVENTS];
414static int Android_NumLifecycleEvents;
415
416/*******************************************************************************
417 Functions called by JNI
418*******************************************************************************/
419
420/* From http://developer.android.com/guide/practices/jni.html
421 * All threads are Linux threads, scheduled by the kernel.
422 * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
423 * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
424 * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
425 * and cannot make JNI calls.
426 * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
427 * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
428 * is a no-op.
429 * Note: You can call this function any number of times for the same thread, there's no harm in it
430 */
431
432/* From http://developer.android.com/guide/practices/jni.html
433 * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
434 * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
435 * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
436 * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
437 * Note: The destructor is not called unless the stored value is != NULL
438 * Note: You can call this function any number of times for the same thread, there's no harm in it
439 * (except for some lost CPU cycles)
440 */
441
442// Set local storage value
443static bool Android_JNI_SetEnv(JNIEnv *env)
444{
445 int status = pthread_setspecific(mThreadKey, env);
446 if (status < 0) {
447 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed pthread_setspecific() in Android_JNI_SetEnv() (err=%d)", status);
448 return false;
449 }
450 return true;
451}
452
453// Get local storage value
454JNIEnv *Android_JNI_GetEnv(void)
455{
456 // Get JNIEnv from the Thread local storage
457 JNIEnv *env = pthread_getspecific(mThreadKey);
458 if (!env) {
459 // If it fails, try to attach ! (e.g the thread isn't created with SDL_CreateThread()
460 int status;
461
462 // There should be a JVM
463 if (!mJavaVM) {
464 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM");
465 return NULL;
466 }
467
468 /* Attach the current thread to the JVM and get a JNIEnv.
469 * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */
470 status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
471 if (status < 0) {
472 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status);
473 return NULL;
474 }
475
476 // Save JNIEnv into the Thread local storage
477 if (!Android_JNI_SetEnv(env)) {
478 return NULL;
479 }
480 }
481
482 return env;
483}
484
485// Set up an external thread for using JNI with Android_JNI_GetEnv()
486bool Android_JNI_SetupThread(void)
487{
488 JNIEnv *env;
489 int status;
490
491 // There should be a JVM
492 if (!mJavaVM) {
493 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed, there is no JavaVM");
494 return false;
495 }
496
497 /* Attach the current thread to the JVM and get a JNIEnv.
498 * It will be detached by pthread_create destructor 'Android_JNI_ThreadDestroyed' */
499 status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
500 if (status < 0) {
501 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to attach current thread (err=%d)", status);
502 return false;
503 }
504
505 // Save JNIEnv into the Thread local storage
506 if (!Android_JNI_SetEnv(env)) {
507 return false;
508 }
509
510 return true;
511}
512
513// Destructor called for each thread where mThreadKey is not NULL
514static void Android_JNI_ThreadDestroyed(void *value)
515{
516 // The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required
517 JNIEnv *env = (JNIEnv *)value;
518 if (env) {
519 (*mJavaVM)->DetachCurrentThread(mJavaVM);
520 Android_JNI_SetEnv(NULL);
521 }
522}
523
524// Creation of local storage mThreadKey
525static void Android_JNI_CreateKey(void)
526{
527 int status = pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed);
528 if (status < 0) {
529 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_key_create() (err=%d)", status);
530 }
531}
532
533static void Android_JNI_CreateKey_once(void)
534{
535 int status = pthread_once(&key_once, Android_JNI_CreateKey);
536 if (status < 0) {
537 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing mThreadKey with pthread_once() (err=%d)", status);
538 }
539}
540
541static void register_methods(JNIEnv *env, const char *classname, JNINativeMethod *methods, int nb)
542{
543 jclass clazz = (*env)->FindClass(env, classname);
544 if (!clazz || (*env)->RegisterNatives(env, clazz, methods, nb) < 0) {
545 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to register methods of %s", classname);
546 return;
547 }
548}
549
550// Library init
551JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
552{
553 JNIEnv *env = NULL;
554
555 mJavaVM = vm;
556
557 if ((*mJavaVM)->GetEnv(mJavaVM, (void **)&env, JNI_VERSION_1_4) != JNI_OK) {
558 __android_log_print(ANDROID_LOG_ERROR, "SDL", "Failed to get JNI Env");
559 return JNI_VERSION_1_4;
560 }
561
562 register_methods(env, "org/libsdl/app/SDLActivity", SDLActivity_tab, SDL_arraysize(SDLActivity_tab));
563 register_methods(env, "org/libsdl/app/SDLInputConnection", SDLInputConnection_tab, SDL_arraysize(SDLInputConnection_tab));
564 register_methods(env, "org/libsdl/app/SDLAudioManager", SDLAudioManager_tab, SDL_arraysize(SDLAudioManager_tab));
565 register_methods(env, "org/libsdl/app/SDLControllerManager", SDLControllerManager_tab, SDL_arraysize(SDLControllerManager_tab));
566 register_methods(env, "org/libsdl/app/HIDDeviceManager", HIDDeviceManager_tab, SDL_arraysize(HIDDeviceManager_tab));
567 SDL_SetAtomicInt(&bAllowRecreateActivity, false);
568
569 return JNI_VERSION_1_4;
570}
571
572void checkJNIReady(void)
573{
574 if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
575 // We aren't fully initialized, let's just return.
576 return;
577 }
578
579 SDL_SetMainReady();
580}
581
582// Get SDL version -- called before SDL_main() to verify JNI bindings
583JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetVersion)(JNIEnv *env, jclass cls)
584{
585 char version[128];
586
587 SDL_snprintf(version, sizeof(version), "%d.%d.%d", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION);
588
589 return (*env)->NewStringUTF(env, version);
590}
591
592// Activity initialization -- called before SDL_main() to initialize JNI bindings
593JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
594{
595 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
596
597 // Start with a clean slate
598 SDL_ClearError();
599
600 /*
601 * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
602 * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
603 */
604 Android_JNI_CreateKey_once();
605
606 // Save JNIEnv of SDLActivity
607 Android_JNI_SetEnv(env);
608
609 if (!mJavaVM) {
610 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to found a JavaVM");
611 }
612
613 /* Use a mutex to prevent concurrency issues between Java Activity and Native thread code, when using 'Android_Window'.
614 * (Eg. Java sending Touch events, while native code is destroying the main SDL_Window. )
615 */
616 if (!Android_ActivityMutex) {
617 Android_ActivityMutex = SDL_CreateMutex(); // Could this be created twice if onCreate() is called a second time ?
618 }
619
620 if (!Android_ActivityMutex) {
621 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_ActivityMutex mutex");
622 }
623
624 Android_LifecycleMutex = SDL_CreateMutex();
625 if (!Android_LifecycleMutex) {
626 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleMutex mutex");
627 }
628
629 Android_LifecycleEventSem = SDL_CreateSemaphore(0);
630 if (!Android_LifecycleEventSem) {
631 __android_log_print(ANDROID_LOG_ERROR, "SDL", "failed to create Android_LifecycleEventSem semaphore");
632 }
633
634 mActivityClass = (jclass)((*env)->NewGlobalRef(env, cls));
635
636 midClipboardGetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardGetText", "()Ljava/lang/String;");
637 midClipboardHasText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardHasText", "()Z");
638 midClipboardSetText = (*env)->GetStaticMethodID(env, mActivityClass, "clipboardSetText", "(Ljava/lang/String;)V");
639 midCreateCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "createCustomCursor", "([IIIII)I");
640 midDestroyCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "destroyCustomCursor", "(I)V");
641 midGetContext = (*env)->GetStaticMethodID(env, mActivityClass, "getContext", "()Landroid/content/Context;");
642 midGetManifestEnvironmentVariables = (*env)->GetStaticMethodID(env, mActivityClass, "getManifestEnvironmentVariables", "()Z");
643 midGetNativeSurface = (*env)->GetStaticMethodID(env, mActivityClass, "getNativeSurface", "()Landroid/view/Surface;");
644 midInitTouch = (*env)->GetStaticMethodID(env, mActivityClass, "initTouch", "()V");
645 midIsAndroidTV = (*env)->GetStaticMethodID(env, mActivityClass, "isAndroidTV", "()Z");
646 midIsChromebook = (*env)->GetStaticMethodID(env, mActivityClass, "isChromebook", "()Z");
647 midIsDeXMode = (*env)->GetStaticMethodID(env, mActivityClass, "isDeXMode", "()Z");
648 midIsScreenKeyboardShown = (*env)->GetStaticMethodID(env, mActivityClass, "isScreenKeyboardShown", "()Z");
649 midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z");
650 midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
651 midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow", "()V");
652 midOpenURL = (*env)->GetStaticMethodID(env, mActivityClass, "openURL", "(Ljava/lang/String;)Z");
653 midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V");
654 midShowToast = (*env)->GetStaticMethodID(env, mActivityClass, "showToast", "(Ljava/lang/String;IIII)Z");
655 midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
656 midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle", "(Ljava/lang/String;)Z");
657 midSetCustomCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setCustomCursor", "(I)Z");
658 midSetOrientation = (*env)->GetStaticMethodID(env, mActivityClass, "setOrientation", "(IIZLjava/lang/String;)V");
659 midSetRelativeMouseEnabled = (*env)->GetStaticMethodID(env, mActivityClass, "setRelativeMouseEnabled", "(Z)Z");
660 midSetSystemCursor = (*env)->GetStaticMethodID(env, mActivityClass, "setSystemCursor", "(I)Z");
661 midSetWindowStyle = (*env)->GetStaticMethodID(env, mActivityClass, "setWindowStyle", "(Z)V");
662 midShouldMinimizeOnFocusLoss = (*env)->GetStaticMethodID(env, mActivityClass, "shouldMinimizeOnFocusLoss", "()Z");
663 midShowTextInput = (*env)->GetStaticMethodID(env, mActivityClass, "showTextInput", "(IIIII)Z");
664 midSupportsRelativeMouse = (*env)->GetStaticMethodID(env, mActivityClass, "supportsRelativeMouse", "()Z");
665 midOpenFileDescriptor = (*env)->GetStaticMethodID(env, mActivityClass, "openFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
666 midShowFileDialog = (*env)->GetStaticMethodID(env, mActivityClass, "showFileDialog", "([Ljava/lang/String;ZZI)Z");
667
668 if (!midClipboardGetText ||
669 !midClipboardHasText ||
670 !midClipboardSetText ||
671 !midCreateCustomCursor ||
672 !midDestroyCustomCursor ||
673 !midGetContext ||
674 !midGetManifestEnvironmentVariables ||
675 !midGetNativeSurface ||
676 !midInitTouch ||
677 !midIsAndroidTV ||
678 !midIsChromebook ||
679 !midIsDeXMode ||
680 !midIsScreenKeyboardShown ||
681 !midIsTablet ||
682 !midManualBackButton ||
683 !midMinimizeWindow ||
684 !midOpenURL ||
685 !midRequestPermission ||
686 !midShowToast ||
687 !midSendMessage ||
688 !midSetActivityTitle ||
689 !midSetCustomCursor ||
690 !midSetOrientation ||
691 !midSetRelativeMouseEnabled ||
692 !midSetSystemCursor ||
693 !midSetWindowStyle ||
694 !midShouldMinimizeOnFocusLoss ||
695 !midShowTextInput ||
696 !midSupportsRelativeMouse ||
697 !midOpenFileDescriptor ||
698 !midShowFileDialog) {
699 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
700 }
701
702 checkJNIReady();
703}
704
705// Audio initialization -- called before SDL_main() to initialize JNI bindings
706JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
707{
708 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
709
710 mAudioManagerClass = (jclass)((*env)->NewGlobalRef(env, cls));
711
712 midRegisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass,
713 "registerAudioDeviceCallback",
714 "()V");
715 midUnregisterAudioDeviceCallback = (*env)->GetStaticMethodID(env, mAudioManagerClass,
716 "unregisterAudioDeviceCallback",
717 "()V");
718 midAudioSetThreadPriority = (*env)->GetStaticMethodID(env, mAudioManagerClass,
719 "audioSetThreadPriority", "(ZI)V");
720
721 if (!midRegisterAudioDeviceCallback || !midUnregisterAudioDeviceCallback || !midAudioSetThreadPriority) {
722 __android_log_print(ANDROID_LOG_WARN, "SDL",
723 "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
724 }
725
726 checkJNIReady();
727}
728
729// Controller initialization -- called before SDL_main() to initialize JNI bindings
730JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cls)
731{
732 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
733
734 mControllerManagerClass = (jclass)((*env)->NewGlobalRef(env, cls));
735
736 midPollInputDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
737 "pollInputDevices", "()V");
738 midPollHapticDevices = (*env)->GetStaticMethodID(env, mControllerManagerClass,
739 "pollHapticDevices", "()V");
740 midHapticRun = (*env)->GetStaticMethodID(env, mControllerManagerClass,
741 "hapticRun", "(IFI)V");
742 midHapticRumble = (*env)->GetStaticMethodID(env, mControllerManagerClass,
743 "hapticRumble", "(IFFI)V");
744 midHapticStop = (*env)->GetStaticMethodID(env, mControllerManagerClass,
745 "hapticStop", "(I)V");
746
747 if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun || !midHapticRumble || !midHapticStop) {
748 __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
749 }
750
751 checkJNIReady();
752}
753
754// SDL main function prototype
755typedef int (*SDL_main_func)(int argc, char *argv[]);
756
757static int run_count = 0;
758
759JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeCheckSDLThreadCounter)(
760 JNIEnv *env, jclass jcls)
761{
762 int tmp = run_count;
763 run_count += 1;
764 return tmp;
765}
766
767static void SDLCALL SDL_AllowRecreateActivityChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
768{
769 if (SDL_GetStringBoolean(hint, false)) {
770 SDL_SetAtomicInt(&bAllowRecreateActivity, true);
771 } else {
772 SDL_SetAtomicInt(&bAllowRecreateActivity, false);
773 }
774}
775
776JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeAllowRecreateActivity)(
777 JNIEnv *env, jclass jcls)
778{
779 return SDL_GetAtomicInt(&bAllowRecreateActivity);
780}
781
782JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeInitMainThread)(
783 JNIEnv *env, jclass jcls)
784{
785 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeInitSDLThread() %d time", run_count);
786 if (run_count == 1) {
787 SDL_AddHintCallback(SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, SDL_AllowRecreateActivityChanged, NULL);
788 }
789 run_count += 1;
790
791 // Save JNIEnv of SDLThread
792 Android_JNI_SetEnv(env);
793}
794
795JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeCleanupMainThread)(
796 JNIEnv *env, jclass jcls)
797{
798 /* This is a Java thread, it doesn't need to be Detached from the JVM.
799 * Set to mThreadKey value to NULL not to call pthread_create destructor 'Android_JNI_ThreadDestroyed' */
800 Android_JNI_SetEnv(NULL);
801}
802
803// Start up the SDL app
804JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv *env, jclass cls, jstring library, jstring function, jobject array)
805{
806 int status = -1;
807 const char *library_file;
808 void *library_handle;
809
810 library_file = (*env)->GetStringUTFChars(env, library, NULL);
811 library_handle = dlopen(library_file, RTLD_GLOBAL);
812
813 if (library_handle == NULL) {
814 /* When deploying android app bundle format uncompressed native libs may not extract from apk to filesystem.
815 In this case we should use lib name without path. https://bugzilla.libsdl.org/show_bug.cgi?id=4739 */
816 const char *library_name = SDL_strrchr(library_file, '/');
817 if (library_name && *library_name) {
818 library_name += 1;
819 library_handle = dlopen(library_name, RTLD_GLOBAL);
820 }
821 }
822
823 if (library_handle) {
824 const char *function_name;
825 SDL_main_func SDL_main;
826
827 function_name = (*env)->GetStringUTFChars(env, function, NULL);
828 SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
829 if (SDL_main) {
830 int i;
831 int argc;
832 int len;
833 char **argv;
834 bool isstack;
835
836 // Prepare the arguments.
837 len = (*env)->GetArrayLength(env, array);
838 argv = SDL_small_alloc(char *, 1 + len + 1, &isstack); // !!! FIXME: check for NULL
839 argc = 0;
840 /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
841 https://github.com/love2d/love-android/issues/24
842 */
843 argv[argc++] = SDL_strdup("app_process");
844 for (i = 0; i < len; ++i) {
845 char *arg = NULL;
846 jstring string = (*env)->GetObjectArrayElement(env, array, i);
847 if (string) {
848 const char *utf = (*env)->GetStringUTFChars(env, string, 0);
849 if (utf) {
850 arg = SDL_strdup(utf);
851 (*env)->ReleaseStringUTFChars(env, string, utf);
852 }
853 (*env)->DeleteLocalRef(env, string);
854 }
855 if (arg == NULL) {
856 arg = SDL_strdup("");
857 }
858 argv[argc++] = arg;
859 }
860 argv[argc] = NULL;
861
862 // Run the application.
863 status = SDL_main(argc, argv);
864
865 // Release the arguments.
866 for (i = 0; i < argc; ++i) {
867 SDL_free(argv[i]);
868 }
869 SDL_small_free(argv, isstack);
870
871 } else {
872 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
873 }
874 (*env)->ReleaseStringUTFChars(env, function, function_name);
875
876 dlclose(library_handle);
877
878 } else {
879 __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
880 }
881 (*env)->ReleaseStringUTFChars(env, library, library_file);
882
883 // Do not issue an exit or the whole application will terminate instead of just the SDL thread
884 // exit(status);
885
886 return status;
887}
888
889static int FindLifecycleEvent(SDL_AndroidLifecycleEvent event)
890{
891 for (int index = 0; index < Android_NumLifecycleEvents; ++index) {
892 if (Android_LifecycleEvents[index] == event) {
893 return index;
894 }
895 }
896 return -1;
897}
898
899static void RemoveLifecycleEvent(int index)
900{
901 if (index < Android_NumLifecycleEvents - 1) {
902 SDL_memcpy(&Android_LifecycleEvents[index], &Android_LifecycleEvents[index+1], (Android_NumLifecycleEvents - index - 1) * sizeof(Android_LifecycleEvents[index]));
903 }
904 --Android_NumLifecycleEvents;
905}
906
907void Android_SendLifecycleEvent(SDL_AndroidLifecycleEvent event)
908{
909 SDL_LockMutex(Android_LifecycleMutex);
910 {
911 int index;
912 bool add_event = true;
913
914 switch (event) {
915 case SDL_ANDROID_LIFECYCLE_WAKE:
916 // We don't need more than one wake queued
917 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_WAKE);
918 if (index >= 0) {
919 add_event = false;
920 }
921 break;
922 case SDL_ANDROID_LIFECYCLE_PAUSE:
923 // If we have a resume queued, just stay in the paused state
924 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME);
925 if (index >= 0) {
926 RemoveLifecycleEvent(index);
927 add_event = false;
928 }
929 break;
930 case SDL_ANDROID_LIFECYCLE_RESUME:
931 // If we have a pause queued, just stay in the resumed state
932 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE);
933 if (index >= 0) {
934 RemoveLifecycleEvent(index);
935 add_event = false;
936 }
937 break;
938 case SDL_ANDROID_LIFECYCLE_LOWMEMORY:
939 // We don't need more than one low memory event queued
940 index = FindLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY);
941 if (index >= 0) {
942 add_event = false;
943 }
944 break;
945 case SDL_ANDROID_LIFECYCLE_DESTROY:
946 // Remove all other events, we're done!
947 while (Android_NumLifecycleEvents > 0) {
948 RemoveLifecycleEvent(0);
949 }
950 break;
951 default:
952 SDL_assert(!"Sending unexpected lifecycle event");
953 add_event = false;
954 break;
955 }
956
957 if (add_event) {
958 SDL_assert(Android_NumLifecycleEvents < SDL_arraysize(Android_LifecycleEvents));
959 Android_LifecycleEvents[Android_NumLifecycleEvents++] = event;
960 SDL_SignalSemaphore(Android_LifecycleEventSem);
961 }
962 }
963 SDL_UnlockMutex(Android_LifecycleMutex);
964}
965
966bool Android_WaitLifecycleEvent(SDL_AndroidLifecycleEvent *event, Sint64 timeoutNS)
967{
968 bool got_event = false;
969
970 while (!got_event && SDL_WaitSemaphoreTimeoutNS(Android_LifecycleEventSem, timeoutNS)) {
971 SDL_LockMutex(Android_LifecycleMutex);
972 {
973 if (Android_NumLifecycleEvents > 0) {
974 *event = Android_LifecycleEvents[0];
975 RemoveLifecycleEvent(0);
976 got_event = true;
977 }
978 }
979 SDL_UnlockMutex(Android_LifecycleMutex);
980 }
981 return got_event;
982}
983
984void Android_LockActivityMutex(void)
985{
986 SDL_LockMutex(Android_ActivityMutex);
987}
988
989void Android_UnlockActivityMutex(void)
990{
991 SDL_UnlockMutex(Android_ActivityMutex);
992}
993
994// Drop file
995JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
996 JNIEnv *env, jclass jcls,
997 jstring filename)
998{
999 const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
1000 SDL_SendDropFile(NULL, NULL, path);
1001 (*env)->ReleaseStringUTFChars(env, filename, path);
1002 SDL_SendDropComplete(NULL);
1003}
1004
1005// Set screen resolution
1006JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetScreenResolution)(
1007 JNIEnv *env, jclass jcls,
1008 jint surfaceWidth, jint surfaceHeight,
1009 jint deviceWidth, jint deviceHeight, jfloat density, jfloat rate)
1010{
1011 SDL_LockMutex(Android_ActivityMutex);
1012
1013 Android_SetScreenResolution(surfaceWidth, surfaceHeight, deviceWidth, deviceHeight, density, rate);
1014
1015 SDL_UnlockMutex(Android_ActivityMutex);
1016}
1017
1018// Resize
1019JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
1020 JNIEnv *env, jclass jcls)
1021{
1022 SDL_LockMutex(Android_ActivityMutex);
1023
1024 if (Android_Window) {
1025 Android_SendResize(Android_Window);
1026 }
1027
1028 SDL_UnlockMutex(Android_ActivityMutex);
1029}
1030
1031JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetNaturalOrientation)(
1032 JNIEnv *env, jclass jcls,
1033 jint orientation)
1034{
1035 displayNaturalOrientation = (SDL_DisplayOrientation)orientation;
1036}
1037
1038JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeRotationChanged)(
1039 JNIEnv *env, jclass jcls,
1040 jint rotation)
1041{
1042 SDL_LockMutex(Android_ActivityMutex);
1043
1044 if (displayNaturalOrientation == SDL_ORIENTATION_LANDSCAPE) {
1045 rotation += 90;
1046 }
1047
1048 switch (rotation % 360) {
1049 case 0:
1050 displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT;
1051 break;
1052 case 90:
1053 displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE;
1054 break;
1055 case 180:
1056 displayCurrentOrientation = SDL_ORIENTATION_PORTRAIT_FLIPPED;
1057 break;
1058 case 270:
1059 displayCurrentOrientation = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
1060 break;
1061 default:
1062 displayCurrentOrientation = SDL_ORIENTATION_UNKNOWN;
1063 break;
1064 }
1065
1066 Android_SetOrientation(displayCurrentOrientation);
1067
1068 SDL_UnlockMutex(Android_ActivityMutex);
1069}
1070
1071JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeInsetsChanged)(
1072 JNIEnv *env, jclass jcls,
1073 jint left, jint right, jint top, jint bottom)
1074{
1075 SDL_LockMutex(Android_ActivityMutex);
1076
1077 Android_SetWindowSafeAreaInsets(left, right, top, bottom);
1078
1079 SDL_UnlockMutex(Android_ActivityMutex);
1080}
1081
1082JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeAddTouch)(
1083 JNIEnv *env, jclass cls,
1084 jint touchId, jstring name)
1085{
1086 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
1087
1088 SDL_AddTouch((SDL_TouchID)touchId, SDL_TOUCH_DEVICE_DIRECT, utfname);
1089
1090 (*env)->ReleaseStringUTFChars(env, name, utfname);
1091}
1092
1093JNIEXPORT void JNICALL
1094SDL_JAVA_AUDIO_INTERFACE(addAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
1095 jstring name, jint device_id)
1096{
1097#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
1098 if (SDL_GetCurrentAudioDriver() != NULL) {
1099 void *handle = (void *)((size_t)device_id);
1100 if (!SDL_FindPhysicalAudioDeviceByHandle(handle)) {
1101 const char *utf8name = (*env)->GetStringUTFChars(env, name, NULL);
1102 SDL_AddAudioDevice(recording, SDL_strdup(utf8name), NULL, handle);
1103 (*env)->ReleaseStringUTFChars(env, name, utf8name);
1104 }
1105 }
1106#endif
1107}
1108
1109JNIEXPORT void JNICALL
1110SDL_JAVA_AUDIO_INTERFACE(removeAudioDevice)(JNIEnv *env, jclass jcls, jboolean recording,
1111 jint device_id)
1112{
1113#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
1114 if (SDL_GetCurrentAudioDriver() != NULL) {
1115 SDL_Log("Removing device with handle %d, recording %d", device_id, recording);
1116 SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)device_id)));
1117 }
1118#endif
1119}
1120
1121// Paddown
1122JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
1123 JNIEnv *env, jclass jcls,
1124 jint device_id, jint keycode)
1125{
1126#ifdef SDL_JOYSTICK_ANDROID
1127 return Android_OnPadDown(device_id, keycode);
1128#else
1129 return false;
1130#endif // SDL_JOYSTICK_ANDROID
1131}
1132
1133// Padup
1134JNIEXPORT jboolean JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
1135 JNIEnv *env, jclass jcls,
1136 jint device_id, jint keycode)
1137{
1138#ifdef SDL_JOYSTICK_ANDROID
1139 return Android_OnPadUp(device_id, keycode);
1140#else
1141 return false;
1142#endif // SDL_JOYSTICK_ANDROID
1143}
1144
1145// Joy
1146JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
1147 JNIEnv *env, jclass jcls,
1148 jint device_id, jint axis, jfloat value)
1149{
1150#ifdef SDL_JOYSTICK_ANDROID
1151 Android_OnJoy(device_id, axis, value);
1152#endif // SDL_JOYSTICK_ANDROID
1153}
1154
1155// POV Hat
1156JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
1157 JNIEnv *env, jclass jcls,
1158 jint device_id, jint hat_id, jint x, jint y)
1159{
1160#ifdef SDL_JOYSTICK_ANDROID
1161 Android_OnHat(device_id, hat_id, x, y);
1162#endif // SDL_JOYSTICK_ANDROID
1163}
1164
1165JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
1166 JNIEnv *env, jclass jcls,
1167 jint device_id, jstring device_name, jstring device_desc,
1168 jint vendor_id, jint product_id,
1169 jint button_mask, jint naxes, jint axis_mask, jint nhats, jboolean can_rumble)
1170{
1171#ifdef SDL_JOYSTICK_ANDROID
1172 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
1173 const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
1174
1175 Android_AddJoystick(device_id, name, desc, vendor_id, product_id, button_mask, naxes, axis_mask, nhats, can_rumble);
1176
1177 (*env)->ReleaseStringUTFChars(env, device_name, name);
1178 (*env)->ReleaseStringUTFChars(env, device_desc, desc);
1179#endif // SDL_JOYSTICK_ANDROID
1180}
1181
1182JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
1183 JNIEnv *env, jclass jcls,
1184 jint device_id)
1185{
1186#ifdef SDL_JOYSTICK_ANDROID
1187 Android_RemoveJoystick(device_id);
1188#endif // SDL_JOYSTICK_ANDROID
1189}
1190
1191JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
1192 JNIEnv *env, jclass jcls, jint device_id, jstring device_name)
1193{
1194#ifdef SDL_HAPTIC_ANDROID
1195 const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
1196
1197 Android_AddHaptic(device_id, name);
1198
1199 (*env)->ReleaseStringUTFChars(env, device_name, name);
1200#endif // SDL_HAPTIC_ANDROID
1201}
1202
1203JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
1204 JNIEnv *env, jclass jcls, jint device_id)
1205{
1206#ifdef SDL_HAPTIC_ANDROID
1207 Android_RemoveHaptic(device_id);
1208#endif
1209}
1210
1211// Called from surfaceCreated()
1212JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceCreated)(JNIEnv *env, jclass jcls)
1213{
1214 SDL_LockMutex(Android_ActivityMutex);
1215
1216 if (Android_Window) {
1217 SDL_WindowData *data = Android_Window->internal;
1218
1219 data->native_window = Android_JNI_GetNativeWindow();
1220 SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, data->native_window);
1221 if (data->native_window == NULL) {
1222 SDL_SetError("Could not fetch native window from UI thread");
1223 }
1224 }
1225
1226 SDL_UnlockMutex(Android_ActivityMutex);
1227}
1228
1229// Called from surfaceChanged()
1230JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv *env, jclass jcls)
1231{
1232 SDL_LockMutex(Android_ActivityMutex);
1233
1234#ifdef SDL_VIDEO_OPENGL_EGL
1235 if (Android_Window && (Android_Window->flags & SDL_WINDOW_OPENGL)) {
1236 SDL_VideoDevice *_this = SDL_GetVideoDevice();
1237 SDL_WindowData *data = Android_Window->internal;
1238
1239 // If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here
1240 if (data->egl_surface == EGL_NO_SURFACE) {
1241 data->egl_surface = SDL_EGL_CreateSurface(_this, Android_Window, (NativeWindowType)data->native_window);
1242 SDL_SetPointerProperty(SDL_GetWindowProperties(Android_Window), SDL_PROP_WINDOW_ANDROID_SURFACE_POINTER, data->egl_surface);
1243 }
1244
1245 // GL Context handling is done in the event loop because this function is run from the Java thread
1246 }
1247#endif
1248
1249 SDL_UnlockMutex(Android_ActivityMutex);
1250}
1251
1252// Called from surfaceDestroyed()
1253JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv *env, jclass jcls)
1254{
1255 int nb_attempt = 50;
1256
1257retry:
1258
1259 SDL_LockMutex(Android_ActivityMutex);
1260
1261 if (Android_Window) {
1262 SDL_WindowData *data = Android_Window->internal;
1263
1264 // Wait for Main thread being paused and context un-activated to release 'egl_surface'
1265 if ((Android_Window->flags & SDL_WINDOW_OPENGL) && !data->backup_done) {
1266 nb_attempt -= 1;
1267 if (nb_attempt == 0) {
1268 SDL_SetError("Try to release egl_surface with context probably still active");
1269 } else {
1270 SDL_UnlockMutex(Android_ActivityMutex);
1271 SDL_Delay(10);
1272 goto retry;
1273 }
1274 }
1275
1276#ifdef SDL_VIDEO_OPENGL_EGL
1277 if (data->egl_surface != EGL_NO_SURFACE) {
1278 SDL_EGL_DestroySurface(SDL_GetVideoDevice(), data->egl_surface);
1279 data->egl_surface = EGL_NO_SURFACE;
1280 }
1281#endif
1282
1283 if (data->native_window) {
1284 ANativeWindow_release(data->native_window);
1285 data->native_window = NULL;
1286 }
1287
1288 // GL Context handling is done in the event loop because this function is run from the Java thread
1289 }
1290
1291 SDL_UnlockMutex(Android_ActivityMutex);
1292}
1293
1294// Keydown
1295JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
1296 JNIEnv *env, jclass jcls,
1297 jint keycode)
1298{
1299 SDL_LockMutex(Android_ActivityMutex);
1300
1301 if (Android_Window) {
1302 Android_OnKeyDown(keycode);
1303 }
1304
1305 SDL_UnlockMutex(Android_ActivityMutex);
1306}
1307
1308// Keyup
1309JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
1310 JNIEnv *env, jclass jcls,
1311 jint keycode)
1312{
1313 SDL_LockMutex(Android_ActivityMutex);
1314
1315 if (Android_Window) {
1316 Android_OnKeyUp(keycode);
1317 }
1318
1319 SDL_UnlockMutex(Android_ActivityMutex);
1320}
1321
1322// Virtual keyboard return key might stop text input
1323JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(onNativeSoftReturnKey)(
1324 JNIEnv *env, jclass jcls)
1325{
1326 if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
1327 SDL_StopTextInput(Android_Window);
1328 return JNI_TRUE;
1329 }
1330 return JNI_FALSE;
1331}
1332
1333// Keyboard Focus Lost
1334JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
1335 JNIEnv *env, jclass jcls)
1336{
1337 // Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget
1338 SDL_StopTextInput(Android_Window);
1339}
1340
1341// Touch
1342JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
1343 JNIEnv *env, jclass jcls,
1344 jint touch_device_id_in, jint pointer_finger_id_in,
1345 jint action, jfloat x, jfloat y, jfloat p)
1346{
1347 SDL_LockMutex(Android_ActivityMutex);
1348
1349 Android_OnTouch(Android_Window, touch_device_id_in, pointer_finger_id_in, action, x, y, p);
1350
1351 SDL_UnlockMutex(Android_ActivityMutex);
1352}
1353
1354// Mouse
1355JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
1356 JNIEnv *env, jclass jcls,
1357 jint button, jint action, jfloat x, jfloat y, jboolean relative)
1358{
1359 SDL_LockMutex(Android_ActivityMutex);
1360
1361 Android_OnMouse(Android_Window, button, action, x, y, relative);
1362
1363 SDL_UnlockMutex(Android_ActivityMutex);
1364}
1365
1366// Pen
1367JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativePen)(
1368 JNIEnv *env, jclass jcls,
1369 jint pen_id_in, jint button, jint action, jfloat x, jfloat y, jfloat p)
1370{
1371 SDL_LockMutex(Android_ActivityMutex);
1372
1373 Android_OnPen(Android_Window, pen_id_in, button, action, x, y, p);
1374
1375 SDL_UnlockMutex(Android_ActivityMutex);
1376}
1377
1378// Accelerometer
1379JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
1380 JNIEnv *env, jclass jcls,
1381 jfloat x, jfloat y, jfloat z)
1382{
1383 fLastAccelerometer[0] = x;
1384 fLastAccelerometer[1] = y;
1385 fLastAccelerometer[2] = z;
1386 bHasNewData = true;
1387}
1388
1389// Clipboard
1390JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
1391 JNIEnv *env, jclass jcls)
1392{
1393 // TODO: compute new mime types
1394 SDL_SendClipboardUpdate(false, NULL, 0);
1395}
1396
1397// Low memory
1398JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
1399 JNIEnv *env, jclass cls)
1400{
1401 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_LOWMEMORY);
1402}
1403
1404/* Locale
1405 * requires android:configChanges="layoutDirection|locale" in AndroidManifest.xml */
1406JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeLocaleChanged)(
1407 JNIEnv *env, jclass cls)
1408{
1409 SDL_SendAppEvent(SDL_EVENT_LOCALE_CHANGED);
1410}
1411
1412// Dark mode
1413JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDarkModeChanged)(
1414 JNIEnv *env, jclass cls, jboolean enabled)
1415{
1416 Android_SetDarkMode(enabled);
1417}
1418
1419// Send Quit event to "SDLThread" thread
1420JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSendQuit)(
1421 JNIEnv *env, jclass cls)
1422{
1423 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_DESTROY);
1424}
1425
1426// Activity ends
1427JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
1428 JNIEnv *env, jclass cls)
1429{
1430 const char *str;
1431
1432 if (Android_ActivityMutex) {
1433 SDL_DestroyMutex(Android_ActivityMutex);
1434 Android_ActivityMutex = NULL;
1435 }
1436
1437 if (Android_LifecycleMutex) {
1438 SDL_DestroyMutex(Android_LifecycleMutex);
1439 Android_LifecycleMutex = NULL;
1440 }
1441
1442 if (Android_LifecycleEventSem) {
1443 SDL_DestroySemaphore(Android_LifecycleEventSem);
1444 Android_LifecycleEventSem = NULL;
1445 }
1446
1447 Android_NumLifecycleEvents = 0;
1448
1449 Internal_Android_Destroy_AssetManager();
1450
1451 str = SDL_GetError();
1452 if (str && str[0]) {
1453 __android_log_print(ANDROID_LOG_ERROR, "SDL", "SDLActivity thread ends (error=%s)", str);
1454 } else {
1455 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDLActivity thread ends");
1456 }
1457}
1458
1459// Pause
1460JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
1461 JNIEnv *env, jclass cls)
1462{
1463 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
1464
1465 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_PAUSE);
1466}
1467
1468// Resume
1469JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
1470 JNIEnv *env, jclass cls)
1471{
1472 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
1473
1474 Android_SendLifecycleEvent(SDL_ANDROID_LIFECYCLE_RESUME);
1475}
1476
1477JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeFocusChanged)(
1478 JNIEnv *env, jclass cls, jboolean hasFocus)
1479{
1480 SDL_LockMutex(Android_ActivityMutex);
1481
1482 if (Android_Window) {
1483 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeFocusChanged()");
1484 SDL_SendWindowEvent(Android_Window, (hasFocus ? SDL_EVENT_WINDOW_FOCUS_GAINED : SDL_EVENT_WINDOW_FOCUS_LOST), 0, 0);
1485 }
1486
1487 SDL_UnlockMutex(Android_ActivityMutex);
1488}
1489
1490JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
1491 JNIEnv *env, jclass cls,
1492 jstring text, jint newCursorPosition)
1493{
1494 const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
1495
1496 SDL_SendKeyboardText(utftext);
1497
1498 (*env)->ReleaseStringUTFChars(env, text, utftext);
1499}
1500
1501JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
1502 JNIEnv *env, jclass cls,
1503 jchar chUnicode)
1504{
1505 SDL_SendKeyboardUnicodeKey(0, chUnicode);
1506}
1507
1508JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
1509 JNIEnv *env, jclass cls,
1510 jstring name)
1511{
1512 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
1513 const char *hint = SDL_GetHint(utfname);
1514
1515 jstring result = (*env)->NewStringUTF(env, hint);
1516 (*env)->ReleaseStringUTFChars(env, name, utfname);
1517
1518 return result;
1519}
1520
1521JNIEXPORT jboolean JNICALL SDL_JAVA_INTERFACE(nativeGetHintBoolean)(
1522 JNIEnv *env, jclass cls,
1523 jstring name, jboolean default_value)
1524{
1525 jboolean result;
1526
1527 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
1528 result = SDL_GetHintBoolean(utfname, default_value);
1529 (*env)->ReleaseStringUTFChars(env, name, utfname);
1530
1531 return result;
1532}
1533
1534JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
1535 JNIEnv *env, jclass cls,
1536 jstring name, jstring value)
1537{
1538 const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
1539 const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
1540
1541 // This is only called at startup, to initialize the environment
1542 // Note that we call setenv() directly to avoid affecting SDL environments
1543 setenv(utfname, utfvalue, 1); // This should NOT be SDL_setenv()
1544
1545 (*env)->ReleaseStringUTFChars(env, name, utfname);
1546 (*env)->ReleaseStringUTFChars(env, value, utfvalue);
1547}
1548
1549/*******************************************************************************
1550 Functions called by SDL into Java
1551*******************************************************************************/
1552
1553static SDL_AtomicInt s_active;
1554struct LocalReferenceHolder
1555{
1556 JNIEnv *m_env;
1557 const char *m_func;
1558};
1559
1560static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
1561{
1562 struct LocalReferenceHolder refholder;
1563 refholder.m_env = NULL;
1564 refholder.m_func = func;
1565#ifdef DEBUG_JNI
1566 SDL_Log("Entering function %s", func);
1567#endif
1568 return refholder;
1569}
1570
1571static bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
1572{
1573 const int capacity = 16;
1574 if ((*env)->PushLocalFrame(env, capacity) < 0) {
1575 SDL_SetError("Failed to allocate enough JVM local references");
1576 return false;
1577 }
1578 SDL_AtomicIncRef(&s_active);
1579 refholder->m_env = env;
1580 return true;
1581}
1582
1583static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
1584{
1585#ifdef DEBUG_JNI
1586 SDL_Log("Leaving function %s", refholder->m_func);
1587#endif
1588 if (refholder->m_env) {
1589 JNIEnv *env = refholder->m_env;
1590 (*env)->PopLocalFrame(env, NULL);
1591 SDL_AtomicDecRef(&s_active);
1592 }
1593}
1594
1595ANativeWindow *Android_JNI_GetNativeWindow(void)
1596{
1597 ANativeWindow *anw = NULL;
1598 jobject s;
1599 JNIEnv *env = Android_JNI_GetEnv();
1600
1601 s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
1602 if (s) {
1603 anw = ANativeWindow_fromSurface(env, s);
1604 (*env)->DeleteLocalRef(env, s);
1605 }
1606
1607 return anw;
1608}
1609
1610void Android_JNI_SetActivityTitle(const char *title)
1611{
1612 JNIEnv *env = Android_JNI_GetEnv();
1613
1614 jstring jtitle = (*env)->NewStringUTF(env, title);
1615 (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetActivityTitle, jtitle);
1616 (*env)->DeleteLocalRef(env, jtitle);
1617}
1618
1619void Android_JNI_SetWindowStyle(bool fullscreen)
1620{
1621 JNIEnv *env = Android_JNI_GetEnv();
1622 (*env)->CallStaticVoidMethod(env, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
1623}
1624
1625void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
1626{
1627 JNIEnv *env = Android_JNI_GetEnv();
1628
1629 jstring jhint = (*env)->NewStringUTF(env, (hint ? hint : ""));
1630 (*env)->CallStaticVoidMethod(env, mActivityClass, midSetOrientation, w, h, (resizable ? 1 : 0), jhint);
1631 (*env)->DeleteLocalRef(env, jhint);
1632}
1633
1634SDL_DisplayOrientation Android_JNI_GetDisplayNaturalOrientation(void)
1635{
1636 return displayNaturalOrientation;
1637}
1638
1639SDL_DisplayOrientation Android_JNI_GetDisplayCurrentOrientation(void)
1640{
1641 return displayCurrentOrientation;
1642}
1643
1644void Android_JNI_MinizeWindow(void)
1645{
1646 JNIEnv *env = Android_JNI_GetEnv();
1647 (*env)->CallStaticVoidMethod(env, mActivityClass, midMinimizeWindow);
1648}
1649
1650bool Android_JNI_ShouldMinimizeOnFocusLoss(void)
1651{
1652 JNIEnv *env = Android_JNI_GetEnv();
1653 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midShouldMinimizeOnFocusLoss);
1654}
1655
1656bool Android_JNI_GetAccelerometerValues(float values[3])
1657{
1658 bool result = false;
1659
1660 if (bHasNewData) {
1661 int i;
1662 for (i = 0; i < 3; ++i) {
1663 values[i] = fLastAccelerometer[i];
1664 }
1665 bHasNewData = false;
1666 result = true;
1667 }
1668
1669 return result;
1670}
1671
1672/*
1673 * Audio support
1674 */
1675void Android_StartAudioHotplug(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
1676{
1677 JNIEnv *env = Android_JNI_GetEnv();
1678 // this will fire the callback for each existing device right away (which will eventually SDL_AddAudioDevice), and again later when things change.
1679 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midRegisterAudioDeviceCallback);
1680 *default_playback = *default_recording = NULL; // !!! FIXME: how do you decide the default device id?
1681}
1682
1683void Android_StopAudioHotplug(void)
1684{
1685 JNIEnv *env = Android_JNI_GetEnv();
1686 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midUnregisterAudioDeviceCallback);
1687}
1688
1689static void Android_JNI_AudioSetThreadPriority(int recording, int device_id)
1690{
1691 JNIEnv *env = Android_JNI_GetEnv();
1692 (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioSetThreadPriority, recording, device_id);
1693}
1694
1695void Android_AudioThreadInit(SDL_AudioDevice *device)
1696{
1697 Android_JNI_AudioSetThreadPriority((int) device->recording, (int)device->instance_id);
1698}
1699
1700// Test for an exception and call SDL_SetError with its detail if one occurs
1701// If the parameter silent is truthy then SDL_SetError() will not be called.
1702static bool Android_JNI_ExceptionOccurred(bool silent)
1703{
1704 JNIEnv *env = Android_JNI_GetEnv();
1705 jthrowable exception;
1706
1707 // Detect mismatch LocalReferenceHolder_Init/Cleanup
1708 SDL_assert(SDL_GetAtomicInt(&s_active) > 0);
1709
1710 exception = (*env)->ExceptionOccurred(env);
1711 if (exception != NULL) {
1712 jmethodID mid;
1713
1714 // Until this happens most JNI operations have undefined behaviour
1715 (*env)->ExceptionClear(env);
1716
1717 if (!silent) {
1718 jclass exceptionClass = (*env)->GetObjectClass(env, exception);
1719 jclass classClass = (*env)->FindClass(env, "java/lang/Class");
1720 jstring exceptionName;
1721 const char *exceptionNameUTF8;
1722 jstring exceptionMessage;
1723
1724 mid = (*env)->GetMethodID(env, classClass, "getName", "()Ljava/lang/String;");
1725 exceptionName = (jstring)(*env)->CallObjectMethod(env, exceptionClass, mid);
1726 exceptionNameUTF8 = (*env)->GetStringUTFChars(env, exceptionName, 0);
1727
1728 mid = (*env)->GetMethodID(env, exceptionClass, "getMessage", "()Ljava/lang/String;");
1729 exceptionMessage = (jstring)(*env)->CallObjectMethod(env, exception, mid);
1730
1731 if (exceptionMessage != NULL) {
1732 const char *exceptionMessageUTF8 = (*env)->GetStringUTFChars(env, exceptionMessage, 0);
1733 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
1734 (*env)->ReleaseStringUTFChars(env, exceptionMessage, exceptionMessageUTF8);
1735 } else {
1736 SDL_SetError("%s", exceptionNameUTF8);
1737 }
1738
1739 (*env)->ReleaseStringUTFChars(env, exceptionName, exceptionNameUTF8);
1740 }
1741
1742 return true;
1743 }
1744
1745 return false;
1746}
1747
1748static void Internal_Android_Create_AssetManager(void)
1749{
1750
1751 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1752 JNIEnv *env = Android_JNI_GetEnv();
1753 jmethodID mid;
1754 jobject context;
1755 jobject javaAssetManager;
1756
1757 if (!LocalReferenceHolder_Init(&refs, env)) {
1758 LocalReferenceHolder_Cleanup(&refs);
1759 return;
1760 }
1761
1762 // context = SDLActivity.getContext();
1763 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1764
1765 // javaAssetManager = context.getAssets();
1766 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1767 "getAssets", "()Landroid/content/res/AssetManager;");
1768 javaAssetManager = (*env)->CallObjectMethod(env, context, mid);
1769
1770 /**
1771 * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager
1772 * object. Note that the caller is responsible for obtaining and holding a VM reference
1773 * to the jobject to prevent its being garbage collected while the native object is
1774 * in use.
1775 */
1776 javaAssetManagerRef = (*env)->NewGlobalRef(env, javaAssetManager);
1777 asset_manager = AAssetManager_fromJava(env, javaAssetManagerRef);
1778
1779 if (!asset_manager) {
1780 (*env)->DeleteGlobalRef(env, javaAssetManagerRef);
1781 Android_JNI_ExceptionOccurred(true);
1782 }
1783
1784 LocalReferenceHolder_Cleanup(&refs);
1785}
1786
1787static void Internal_Android_Destroy_AssetManager(void)
1788{
1789 JNIEnv *env = Android_JNI_GetEnv();
1790
1791 if (asset_manager) {
1792 (*env)->DeleteGlobalRef(env, javaAssetManagerRef);
1793 asset_manager = NULL;
1794 }
1795}
1796
1797bool Android_JNI_FileOpen(void **puserdata, const char *fileName, const char *mode)
1798{
1799 SDL_assert(puserdata != NULL);
1800
1801 AAsset *asset = NULL;
1802 *puserdata = NULL;
1803
1804 if (!asset_manager) {
1805 Internal_Android_Create_AssetManager();
1806 }
1807
1808 if (!asset_manager) {
1809 return SDL_SetError("Couldn't create asset manager");
1810 }
1811
1812 asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN);
1813 if (!asset) {
1814 return SDL_SetError("Couldn't open asset '%s'", fileName);
1815 }
1816
1817 *puserdata = (void *)asset;
1818 return true;
1819}
1820
1821size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOStatus *status)
1822{
1823 const int bytes = AAsset_read((AAsset *)userdata, buffer, size);
1824 if (bytes < 0) {
1825 SDL_SetError("AAsset_read() failed");
1826 return 0;
1827 }
1828 return (size_t)bytes;
1829}
1830
1831size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status)
1832{
1833 SDL_SetError("Cannot write to Android package filesystem");
1834 return 0;
1835}
1836
1837Sint64 Android_JNI_FileSize(void *userdata)
1838{
1839 return (Sint64) AAsset_getLength64((AAsset *)userdata);
1840}
1841
1842Sint64 Android_JNI_FileSeek(void *userdata, Sint64 offset, SDL_IOWhence whence)
1843{
1844 return (Sint64) AAsset_seek64((AAsset *)userdata, offset, (int)whence);
1845}
1846
1847bool Android_JNI_FileClose(void *userdata)
1848{
1849 AAsset_close((AAsset *)userdata);
1850 return true;
1851}
1852
1853bool Android_JNI_SetClipboardText(const char *text)
1854{
1855 JNIEnv *env = Android_JNI_GetEnv();
1856 jstring string = (*env)->NewStringUTF(env, text);
1857 (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
1858 (*env)->DeleteLocalRef(env, string);
1859 return true;
1860}
1861
1862char *Android_JNI_GetClipboardText(void)
1863{
1864 JNIEnv *env = Android_JNI_GetEnv();
1865 char *text = NULL;
1866 jstring string;
1867
1868 string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
1869 if (string) {
1870 const char *utf = (*env)->GetStringUTFChars(env, string, 0);
1871 if (utf) {
1872 text = SDL_strdup(utf);
1873 (*env)->ReleaseStringUTFChars(env, string, utf);
1874 }
1875 (*env)->DeleteLocalRef(env, string);
1876 }
1877
1878 return (!text) ? SDL_strdup("") : text;
1879}
1880
1881bool Android_JNI_HasClipboardText(void)
1882{
1883 JNIEnv *env = Android_JNI_GetEnv();
1884 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
1885}
1886
1887/* returns 0 on success or -1 on error (others undefined then)
1888 * returns truthy or falsy value in plugged, charged and battery
1889 * returns the value in seconds and percent or -1 if not available
1890 */
1891int Android_JNI_GetPowerInfo(int *plugged, int *charged, int *battery, int *seconds, int *percent)
1892{
1893 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1894 JNIEnv *env = Android_JNI_GetEnv();
1895 jmethodID mid;
1896 jobject context;
1897 jstring action;
1898 jclass cls;
1899 jobject filter;
1900 jobject intent;
1901 jstring iname;
1902 jmethodID imid;
1903 jstring bname;
1904 jmethodID bmid;
1905 if (!LocalReferenceHolder_Init(&refs, env)) {
1906 LocalReferenceHolder_Cleanup(&refs);
1907 return -1;
1908 }
1909
1910 // context = SDLActivity.getContext();
1911 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1912
1913 action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1914
1915 cls = (*env)->FindClass(env, "android/content/IntentFilter");
1916
1917 mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1918 filter = (*env)->NewObject(env, cls, mid, action);
1919
1920 (*env)->DeleteLocalRef(env, action);
1921
1922 mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1923 intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1924
1925 (*env)->DeleteLocalRef(env, filter);
1926
1927 cls = (*env)->GetObjectClass(env, intent);
1928
1929 imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1930
1931 // Watch out for C89 scoping rules because of the macro
1932#define GET_INT_EXTRA(var, key) \
1933 int var; \
1934 iname = (*env)->NewStringUTF(env, key); \
1935 (var) = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1936 (*env)->DeleteLocalRef(env, iname);
1937
1938 bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1939
1940 // Watch out for C89 scoping rules because of the macro
1941#define GET_BOOL_EXTRA(var, key) \
1942 int var; \
1943 bname = (*env)->NewStringUTF(env, key); \
1944 (var) = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1945 (*env)->DeleteLocalRef(env, bname);
1946
1947 if (plugged) {
1948 // Watch out for C89 scoping rules because of the macro
1949 GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
1950 if (plug == -1) {
1951 LocalReferenceHolder_Cleanup(&refs);
1952 return -1;
1953 }
1954 // 1 == BatteryManager.BATTERY_PLUGGED_AC
1955 // 2 == BatteryManager.BATTERY_PLUGGED_USB
1956 *plugged = (0 < plug) ? 1 : 0;
1957 }
1958
1959 if (charged) {
1960 // Watch out for C89 scoping rules because of the macro
1961 GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
1962 if (status == -1) {
1963 LocalReferenceHolder_Cleanup(&refs);
1964 return -1;
1965 }
1966 // 5 == BatteryManager.BATTERY_STATUS_FULL
1967 *charged = (status == 5) ? 1 : 0;
1968 }
1969
1970 if (battery) {
1971 GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
1972 *battery = present ? 1 : 0;
1973 }
1974
1975 if (seconds) {
1976 *seconds = -1; // not possible
1977 }
1978
1979 if (percent) {
1980 int level;
1981 int scale;
1982
1983 // Watch out for C89 scoping rules because of the macro
1984 {
1985 GET_INT_EXTRA(level_temp, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
1986 level = level_temp;
1987 }
1988 // Watch out for C89 scoping rules because of the macro
1989 {
1990 GET_INT_EXTRA(scale_temp, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
1991 scale = scale_temp;
1992 }
1993
1994 if ((level == -1) || (scale == -1)) {
1995 LocalReferenceHolder_Cleanup(&refs);
1996 return -1;
1997 }
1998 *percent = level * 100 / scale;
1999 }
2000
2001 (*env)->DeleteLocalRef(env, intent);
2002
2003 LocalReferenceHolder_Cleanup(&refs);
2004 return 0;
2005}
2006
2007// Add all touch devices
2008void Android_JNI_InitTouch(void)
2009{
2010 JNIEnv *env = Android_JNI_GetEnv();
2011 (*env)->CallStaticVoidMethod(env, mActivityClass, midInitTouch);
2012}
2013
2014void Android_JNI_PollInputDevices(void)
2015{
2016 JNIEnv *env = Android_JNI_GetEnv();
2017 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
2018}
2019
2020void Android_JNI_PollHapticDevices(void)
2021{
2022 JNIEnv *env = Android_JNI_GetEnv();
2023 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
2024}
2025
2026void Android_JNI_HapticRun(int device_id, float intensity, int length)
2027{
2028 JNIEnv *env = Android_JNI_GetEnv();
2029 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, intensity, length);
2030}
2031
2032void Android_JNI_HapticRumble(int device_id, float low_frequency_intensity, float high_frequency_intensity, int length)
2033{
2034 JNIEnv *env = Android_JNI_GetEnv();
2035 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRumble, device_id, low_frequency_intensity, high_frequency_intensity, length);
2036}
2037
2038void Android_JNI_HapticStop(int device_id)
2039{
2040 JNIEnv *env = Android_JNI_GetEnv();
2041 (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticStop, device_id);
2042}
2043
2044// See SDLActivity.java for constants.
2045#define COMMAND_SET_KEEP_SCREEN_ON 5
2046
2047bool SDL_SendAndroidMessage(Uint32 command, int param)
2048{
2049 if (command < 0x8000) {
2050 return SDL_InvalidParamError("command");
2051 }
2052 return Android_JNI_SendMessage(command, param);
2053}
2054
2055// sends message to be handled on the UI event dispatch thread
2056bool Android_JNI_SendMessage(int command, int param)
2057{
2058 JNIEnv *env = Android_JNI_GetEnv();
2059 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
2060}
2061
2062bool Android_JNI_SuspendScreenSaver(bool suspend)
2063{
2064 return Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == false) ? 0 : 1);
2065}
2066
2067void Android_JNI_ShowScreenKeyboard(int input_type, SDL_Rect *inputRect)
2068{
2069 JNIEnv *env = Android_JNI_GetEnv();
2070 (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
2071 input_type,
2072 inputRect->x,
2073 inputRect->y,
2074 inputRect->w,
2075 inputRect->h);
2076}
2077
2078void Android_JNI_HideScreenKeyboard(void)
2079{
2080 // has to match Activity constant
2081 const int COMMAND_TEXTEDIT_HIDE = 3;
2082 Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
2083}
2084
2085bool Android_JNI_IsScreenKeyboardShown(void)
2086{
2087 JNIEnv *env = Android_JNI_GetEnv();
2088 jboolean is_shown = 0;
2089 is_shown = (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsScreenKeyboardShown);
2090 return is_shown;
2091}
2092
2093bool Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
2094{
2095 JNIEnv *env;
2096 jclass clazz;
2097 jmethodID mid;
2098 jobject context;
2099 jstring title;
2100 jstring message;
2101 jintArray button_flags;
2102 jintArray button_ids;
2103 jobjectArray button_texts;
2104 jintArray colors;
2105 jobject text;
2106 jint temp;
2107 int i;
2108
2109 env = Android_JNI_GetEnv();
2110
2111 // convert parameters
2112
2113 clazz = (*env)->FindClass(env, "java/lang/String");
2114
2115 title = (*env)->NewStringUTF(env, messageboxdata->title);
2116 message = (*env)->NewStringUTF(env, messageboxdata->message);
2117
2118 button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
2119 button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
2120 button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
2121 clazz, NULL);
2122 for (i = 0; i < messageboxdata->numbuttons; ++i) {
2123 const SDL_MessageBoxButtonData *sdlButton;
2124
2125 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_RIGHT_TO_LEFT) {
2126 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
2127 } else {
2128 sdlButton = &messageboxdata->buttons[i];
2129 }
2130
2131 temp = sdlButton->flags;
2132 (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
2133 temp = sdlButton->buttonID;
2134 (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
2135 text = (*env)->NewStringUTF(env, sdlButton->text);
2136 (*env)->SetObjectArrayElement(env, button_texts, i, text);
2137 (*env)->DeleteLocalRef(env, text);
2138 }
2139
2140 if (messageboxdata->colorScheme) {
2141 colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_COUNT);
2142 for (i = 0; i < SDL_MESSAGEBOX_COLOR_COUNT; ++i) {
2143 temp = (0xFFU << 24) |
2144 (messageboxdata->colorScheme->colors[i].r << 16) |
2145 (messageboxdata->colorScheme->colors[i].g << 8) |
2146 (messageboxdata->colorScheme->colors[i].b << 0);
2147 (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
2148 }
2149 } else {
2150 colors = NULL;
2151 }
2152
2153 (*env)->DeleteLocalRef(env, clazz);
2154
2155 // context = SDLActivity.getContext();
2156 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2157
2158 clazz = (*env)->GetObjectClass(env, context);
2159
2160 mid = (*env)->GetMethodID(env, clazz,
2161 "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
2162 *buttonID = (*env)->CallIntMethod(env, context, mid,
2163 messageboxdata->flags,
2164 title,
2165 message,
2166 button_flags,
2167 button_ids,
2168 button_texts,
2169 colors);
2170
2171 (*env)->DeleteLocalRef(env, context);
2172 (*env)->DeleteLocalRef(env, clazz);
2173
2174 // delete parameters
2175
2176 (*env)->DeleteLocalRef(env, title);
2177 (*env)->DeleteLocalRef(env, message);
2178 (*env)->DeleteLocalRef(env, button_flags);
2179 (*env)->DeleteLocalRef(env, button_ids);
2180 (*env)->DeleteLocalRef(env, button_texts);
2181 (*env)->DeleteLocalRef(env, colors);
2182
2183 return true;
2184}
2185
2186/*
2187//////////////////////////////////////////////////////////////////////////////
2188//
2189// Functions exposed to SDL applications in SDL_system.h
2190//////////////////////////////////////////////////////////////////////////////
2191*/
2192
2193void *SDL_GetAndroidJNIEnv(void)
2194{
2195 return Android_JNI_GetEnv();
2196}
2197
2198void *SDL_GetAndroidActivity(void)
2199{
2200 // See SDL_system.h for caveats on using this function.
2201
2202 JNIEnv *env = Android_JNI_GetEnv();
2203 if (!env) {
2204 return NULL;
2205 }
2206
2207 // return SDLActivity.getContext();
2208 return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2209}
2210
2211int SDL_GetAndroidSDKVersion(void)
2212{
2213 static int sdk_version;
2214 if (!sdk_version) {
2215 char sdk[PROP_VALUE_MAX] = { 0 };
2216 if (__system_property_get("ro.build.version.sdk", sdk) != 0) {
2217 sdk_version = SDL_atoi(sdk);
2218 }
2219 }
2220 return sdk_version;
2221}
2222
2223bool SDL_IsAndroidTablet(void)
2224{
2225 JNIEnv *env = Android_JNI_GetEnv();
2226 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsTablet);
2227}
2228
2229bool SDL_IsAndroidTV(void)
2230{
2231 JNIEnv *env = Android_JNI_GetEnv();
2232 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
2233}
2234
2235bool SDL_IsChromebook(void)
2236{
2237 JNIEnv *env = Android_JNI_GetEnv();
2238 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsChromebook);
2239}
2240
2241bool SDL_IsDeXMode(void)
2242{
2243 JNIEnv *env = Android_JNI_GetEnv();
2244 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsDeXMode);
2245}
2246
2247void SDL_SendAndroidBackButton(void)
2248{
2249 JNIEnv *env = Android_JNI_GetEnv();
2250 (*env)->CallStaticVoidMethod(env, mActivityClass, midManualBackButton);
2251}
2252
2253const char *SDL_GetAndroidInternalStoragePath(void)
2254{
2255 static char *s_AndroidInternalFilesPath = NULL;
2256
2257 if (!s_AndroidInternalFilesPath) {
2258 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2259 jmethodID mid;
2260 jobject context;
2261 jobject fileObject;
2262 jstring pathString;
2263 const char *path;
2264
2265 JNIEnv *env = Android_JNI_GetEnv();
2266 if (!LocalReferenceHolder_Init(&refs, env)) {
2267 LocalReferenceHolder_Cleanup(&refs);
2268 return NULL;
2269 }
2270
2271 // context = SDLActivity.getContext();
2272 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2273 if (!context) {
2274 SDL_SetError("Couldn't get Android context!");
2275 LocalReferenceHolder_Cleanup(&refs);
2276 return NULL;
2277 }
2278
2279 // fileObj = context.getFilesDir();
2280 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2281 "getFilesDir", "()Ljava/io/File;");
2282 fileObject = (*env)->CallObjectMethod(env, context, mid);
2283 if (!fileObject) {
2284 SDL_SetError("Couldn't get internal directory");
2285 LocalReferenceHolder_Cleanup(&refs);
2286 return NULL;
2287 }
2288
2289 // path = fileObject.getCanonicalPath();
2290 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2291 "getCanonicalPath", "()Ljava/lang/String;");
2292 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2293 if (Android_JNI_ExceptionOccurred(false)) {
2294 LocalReferenceHolder_Cleanup(&refs);
2295 return NULL;
2296 }
2297
2298 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2299 s_AndroidInternalFilesPath = SDL_strdup(path);
2300 (*env)->ReleaseStringUTFChars(env, pathString, path);
2301
2302 LocalReferenceHolder_Cleanup(&refs);
2303 }
2304 return s_AndroidInternalFilesPath;
2305}
2306
2307Uint32 SDL_GetAndroidExternalStorageState(void)
2308{
2309 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2310 jmethodID mid;
2311 jclass cls;
2312 jstring stateString;
2313 const char *state_string;
2314 Uint32 stateFlags;
2315
2316 JNIEnv *env = Android_JNI_GetEnv();
2317 if (!LocalReferenceHolder_Init(&refs, env)) {
2318 LocalReferenceHolder_Cleanup(&refs);
2319 return 0;
2320 }
2321
2322 cls = (*env)->FindClass(env, "android/os/Environment");
2323 mid = (*env)->GetStaticMethodID(env, cls,
2324 "getExternalStorageState", "()Ljava/lang/String;");
2325 stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
2326
2327 state_string = (*env)->GetStringUTFChars(env, stateString, NULL);
2328
2329 // Print an info message so people debugging know the storage state
2330 __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state_string);
2331
2332 if (SDL_strcmp(state_string, "mounted") == 0) {
2333 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
2334 SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
2335 } else if (SDL_strcmp(state_string, "mounted_ro") == 0) {
2336 stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
2337 } else {
2338 stateFlags = 0;
2339 }
2340 (*env)->ReleaseStringUTFChars(env, stateString, state_string);
2341
2342 LocalReferenceHolder_Cleanup(&refs);
2343
2344 return stateFlags;
2345}
2346
2347const char *SDL_GetAndroidExternalStoragePath(void)
2348{
2349 static char *s_AndroidExternalFilesPath = NULL;
2350
2351 if (!s_AndroidExternalFilesPath) {
2352 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2353 jmethodID mid;
2354 jobject context;
2355 jobject fileObject;
2356 jstring pathString;
2357 const char *path;
2358
2359 JNIEnv *env = Android_JNI_GetEnv();
2360 if (!LocalReferenceHolder_Init(&refs, env)) {
2361 LocalReferenceHolder_Cleanup(&refs);
2362 return NULL;
2363 }
2364
2365 // context = SDLActivity.getContext();
2366 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2367
2368 // fileObj = context.getExternalFilesDir();
2369 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2370 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
2371 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2372 if (!fileObject) {
2373 SDL_SetError("Couldn't get external directory");
2374 LocalReferenceHolder_Cleanup(&refs);
2375 return NULL;
2376 }
2377
2378 // path = fileObject.getAbsolutePath();
2379 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2380 "getAbsolutePath", "()Ljava/lang/String;");
2381 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2382
2383 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2384 s_AndroidExternalFilesPath = SDL_strdup(path);
2385 (*env)->ReleaseStringUTFChars(env, pathString, path);
2386
2387 LocalReferenceHolder_Cleanup(&refs);
2388 }
2389 return s_AndroidExternalFilesPath;
2390}
2391
2392const char *SDL_GetAndroidCachePath(void)
2393{
2394 // !!! FIXME: lots of duplication with SDL_GetAndroidExternalStoragePath and SDL_GetAndroidInternalStoragePath; consolidate these functions!
2395 static char *s_AndroidCachePath = NULL;
2396
2397 if (!s_AndroidCachePath) {
2398 struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2399 jmethodID mid;
2400 jobject context;
2401 jobject fileObject;
2402 jstring pathString;
2403 const char *path;
2404
2405 JNIEnv *env = Android_JNI_GetEnv();
2406 if (!LocalReferenceHolder_Init(&refs, env)) {
2407 LocalReferenceHolder_Cleanup(&refs);
2408 return NULL;
2409 }
2410
2411 // context = SDLActivity.getContext();
2412 context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2413
2414 // fileObj = context.getExternalFilesDir();
2415 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2416 "getCacheDir", "()Ljava/io/File;");
2417 fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2418 if (!fileObject) {
2419 SDL_SetError("Couldn't get cache directory");
2420 LocalReferenceHolder_Cleanup(&refs);
2421 return NULL;
2422 }
2423
2424 // path = fileObject.getAbsolutePath();
2425 mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2426 "getAbsolutePath", "()Ljava/lang/String;");
2427 pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2428
2429 path = (*env)->GetStringUTFChars(env, pathString, NULL);
2430 s_AndroidCachePath = SDL_strdup(path);
2431 (*env)->ReleaseStringUTFChars(env, pathString, path);
2432
2433 LocalReferenceHolder_Cleanup(&refs);
2434 }
2435 return s_AndroidCachePath;
2436}
2437
2438bool SDL_ShowAndroidToast(const char *message, int duration, int gravity, int xOffset, int yOffset)
2439{
2440 return Android_JNI_ShowToast(message, duration, gravity, xOffset, yOffset);
2441}
2442
2443void Android_JNI_GetManifestEnvironmentVariables(void)
2444{
2445 if (!mActivityClass || !midGetManifestEnvironmentVariables) {
2446 __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
2447 return;
2448 }
2449
2450 if (!bHasEnvironmentVariables) {
2451 JNIEnv *env = Android_JNI_GetEnv();
2452 bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
2453 if (ret) {
2454 bHasEnvironmentVariables = true;
2455 }
2456 }
2457}
2458
2459int Android_JNI_CreateCustomCursor(SDL_Surface *surface, int hot_x, int hot_y)
2460{
2461 JNIEnv *env = Android_JNI_GetEnv();
2462 int custom_cursor = 0;
2463 jintArray pixels;
2464 pixels = (*env)->NewIntArray(env, surface->w * surface->h);
2465 if (pixels) {
2466 (*env)->SetIntArrayRegion(env, pixels, 0, surface->w * surface->h, (int *)surface->pixels);
2467 custom_cursor = (*env)->CallStaticIntMethod(env, mActivityClass, midCreateCustomCursor, pixels, surface->w, surface->h, hot_x, hot_y);
2468 (*env)->DeleteLocalRef(env, pixels);
2469 } else {
2470 SDL_OutOfMemory();
2471 }
2472 return custom_cursor;
2473}
2474
2475void Android_JNI_DestroyCustomCursor(int cursorID)
2476{
2477 JNIEnv *env = Android_JNI_GetEnv();
2478 (*env)->CallStaticVoidMethod(env, mActivityClass, midDestroyCustomCursor, cursorID);
2479}
2480
2481bool Android_JNI_SetCustomCursor(int cursorID)
2482{
2483 JNIEnv *env = Android_JNI_GetEnv();
2484 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetCustomCursor, cursorID);
2485}
2486
2487bool Android_JNI_SetSystemCursor(int cursorID)
2488{
2489 JNIEnv *env = Android_JNI_GetEnv();
2490 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetSystemCursor, cursorID);
2491}
2492
2493bool Android_JNI_SupportsRelativeMouse(void)
2494{
2495 JNIEnv *env = Android_JNI_GetEnv();
2496 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSupportsRelativeMouse);
2497}
2498
2499bool Android_JNI_SetRelativeMouseEnabled(bool enabled)
2500{
2501 JNIEnv *env = Android_JNI_GetEnv();
2502 return (*env)->CallStaticBooleanMethod(env, mActivityClass, midSetRelativeMouseEnabled, (enabled == 1));
2503}
2504
2505typedef struct NativePermissionRequestInfo
2506{
2507 int request_code;
2508 char *permission;
2509 SDL_RequestAndroidPermissionCallback callback;
2510 void *userdata;
2511 struct NativePermissionRequestInfo *next;
2512} NativePermissionRequestInfo;
2513
2514static NativePermissionRequestInfo pending_permissions;
2515
2516JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePermissionResult)(
2517 JNIEnv *env, jclass cls,
2518 jint requestCode, jboolean result)
2519{
2520 SDL_LockMutex(Android_ActivityMutex);
2521 NativePermissionRequestInfo *prev = &pending_permissions;
2522 for (NativePermissionRequestInfo *info = prev->next; info != NULL; info = info->next) {
2523 if (info->request_code == (int) requestCode) {
2524 prev->next = info->next;
2525 SDL_UnlockMutex(Android_ActivityMutex);
2526 info->callback(info->userdata, info->permission, result ? true : false);
2527 SDL_free(info->permission);
2528 SDL_free(info);
2529 return;
2530 }
2531 prev = info;
2532 }
2533
2534 SDL_UnlockMutex(Android_ActivityMutex);
2535}
2536
2537bool SDL_RequestAndroidPermission(const char *permission, SDL_RequestAndroidPermissionCallback cb, void *userdata)
2538{
2539 if (!permission) {
2540 return SDL_InvalidParamError("permission");
2541 } else if (!cb) {
2542 return SDL_InvalidParamError("cb");
2543 }
2544
2545 NativePermissionRequestInfo *info = (NativePermissionRequestInfo *) SDL_calloc(1, sizeof (NativePermissionRequestInfo));
2546 if (!info) {
2547 return false;
2548 }
2549
2550 info->permission = SDL_strdup(permission);
2551 if (!info->permission) {
2552 SDL_free(info);
2553 return false;
2554 }
2555
2556 static SDL_AtomicInt next_request_code;
2557 info->request_code = SDL_AddAtomicInt(&next_request_code, 1);
2558
2559 info->callback = cb;
2560 info->userdata = userdata;
2561
2562 SDL_LockMutex(Android_ActivityMutex);
2563 info->next = pending_permissions.next;
2564 pending_permissions.next = info;
2565 SDL_UnlockMutex(Android_ActivityMutex);
2566
2567 JNIEnv *env = Android_JNI_GetEnv();
2568 jstring jpermission = (*env)->NewStringUTF(env, permission);
2569 (*env)->CallStaticVoidMethod(env, mActivityClass, midRequestPermission, jpermission, info->request_code);
2570 (*env)->DeleteLocalRef(env, jpermission);
2571
2572 return true;
2573}
2574
2575// Show toast notification
2576bool Android_JNI_ShowToast(const char *message, int duration, int gravity, int xOffset, int yOffset)
2577{
2578 bool result;
2579 JNIEnv *env = Android_JNI_GetEnv();
2580 jstring jmessage = (*env)->NewStringUTF(env, message);
2581 result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowToast, jmessage, duration, gravity, xOffset, yOffset);
2582 (*env)->DeleteLocalRef(env, jmessage);
2583 return result;
2584}
2585
2586bool Android_JNI_GetLocale(char *buf, size_t buflen)
2587{
2588 AConfiguration *cfg;
2589
2590 SDL_assert(buflen > 6);
2591
2592 // Need to re-create the asset manager if locale has changed (SDL_EVENT_LOCALE_CHANGED)
2593 Internal_Android_Destroy_AssetManager();
2594
2595 if (!asset_manager) {
2596 Internal_Android_Create_AssetManager();
2597 }
2598
2599 if (!asset_manager) {
2600 return false;
2601 }
2602
2603 cfg = AConfiguration_new();
2604 if (!cfg) {
2605 return false;
2606 }
2607
2608 {
2609 char language[2] = {};
2610 char country[2] = {};
2611 size_t id = 0;
2612
2613 AConfiguration_fromAssetManager(cfg, asset_manager);
2614 AConfiguration_getLanguage(cfg, language);
2615 AConfiguration_getCountry(cfg, country);
2616
2617 // Indonesian is "id" according to ISO 639.2, but on Android is "in" because of Java backwards compatibility
2618 if (language[0] == 'i' && language[1] == 'n') {
2619 language[1] = 'd';
2620 }
2621
2622 // copy language (not null terminated)
2623 if (language[0]) {
2624 buf[id++] = language[0];
2625 if (language[1]) {
2626 buf[id++] = language[1];
2627 }
2628 }
2629
2630 buf[id++] = '_';
2631
2632 // copy country (not null terminated)
2633 if (country[0]) {
2634 buf[id++] = country[0];
2635 if (country[1]) {
2636 buf[id++] = country[1];
2637 }
2638 }
2639
2640 buf[id++] = '\0';
2641 SDL_assert(id <= buflen);
2642 }
2643
2644 AConfiguration_delete(cfg);
2645
2646 return true;
2647}
2648
2649bool Android_JNI_OpenURL(const char *url)
2650{
2651 bool result;
2652 JNIEnv *env = Android_JNI_GetEnv();
2653 jstring jurl = (*env)->NewStringUTF(env, url);
2654 result = (*env)->CallStaticBooleanMethod(env, mActivityClass, midOpenURL, jurl);
2655 (*env)->DeleteLocalRef(env, jurl);
2656 return result;
2657}
2658
2659int Android_JNI_OpenFileDescriptor(const char *uri, const char *mode)
2660{
2661 // Get fopen-style modes
2662 int moderead = 0, modewrite = 0, modeappend = 0, modeupdate = 0;
2663
2664 for (const char *cmode = mode; *cmode; cmode++) {
2665 switch (*cmode) {
2666 case 'a':
2667 modeappend = 1;
2668 break;
2669 case 'r':
2670 moderead = 1;
2671 break;
2672 case 'w':
2673 modewrite = 1;
2674 break;
2675 case '+':
2676 modeupdate = 1;
2677 break;
2678 default:
2679 break;
2680 }
2681 }
2682
2683 // Translate fopen-style modes to ContentResolver modes.
2684 // Android only allows "r", "w", "wt", "wa", "rw" or "rwt".
2685 const char *contentResolverMode = "r";
2686
2687 if (moderead) {
2688 if (modewrite) {
2689 contentResolverMode = "rwt";
2690 } else {
2691 contentResolverMode = modeupdate ? "rw" : "r";
2692 }
2693 } else if (modewrite) {
2694 contentResolverMode = modeupdate ? "rwt" : "wt";
2695 } else if (modeappend) {
2696 contentResolverMode = modeupdate ? "rw" : "wa";
2697 }
2698
2699 JNIEnv *env = Android_JNI_GetEnv();
2700 jstring jstringUri = (*env)->NewStringUTF(env, uri);
2701 jstring jstringMode = (*env)->NewStringUTF(env, contentResolverMode);
2702 jint fd = (*env)->CallStaticIntMethod(env, mActivityClass, midOpenFileDescriptor, jstringUri, jstringMode);
2703 (*env)->DeleteLocalRef(env, jstringUri);
2704 (*env)->DeleteLocalRef(env, jstringMode);
2705
2706 if (fd == -1) {
2707 SDL_SetError("Unspecified error in JNI");
2708 }
2709
2710 return fd;
2711}
2712
2713static struct AndroidFileDialog
2714{
2715 int request_code;
2716 SDL_DialogFileCallback callback;
2717 void *userdata;
2718} mAndroidFileDialogData;
2719
2720JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeFileDialog)(
2721 JNIEnv *env, jclass jcls,
2722 jint requestCode, jobjectArray fileList, jint filter)
2723{
2724 if (mAndroidFileDialogData.callback != NULL && mAndroidFileDialogData.request_code == requestCode) {
2725 if (fileList == NULL) {
2726 SDL_SetError("Unspecified error in JNI");
2727 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
2728 mAndroidFileDialogData.callback = NULL;
2729 return;
2730 }
2731
2732 // Convert fileList to string
2733 size_t count = (*env)->GetArrayLength(env, fileList);
2734 char **charFileList = SDL_calloc(count + 1, sizeof(char*));
2735
2736 if (charFileList == NULL) {
2737 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
2738 mAndroidFileDialogData.callback = NULL;
2739 return;
2740 }
2741
2742 // Convert to UTF-8
2743 // TODO: Fix modified UTF-8 to classic UTF-8
2744 for (int i = 0; i < count; i++) {
2745 jstring string = (*env)->GetObjectArrayElement(env, fileList, i);
2746 if (!string) {
2747 continue;
2748 }
2749
2750 const char *utf8string = (*env)->GetStringUTFChars(env, string, NULL);
2751 if (!utf8string) {
2752 (*env)->DeleteLocalRef(env, string);
2753 continue;
2754 }
2755
2756 char *newFile = SDL_strdup(utf8string);
2757 if (!newFile) {
2758 (*env)->ReleaseStringUTFChars(env, string, utf8string);
2759 (*env)->DeleteLocalRef(env, string);
2760 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, NULL, -1);
2761 mAndroidFileDialogData.callback = NULL;
2762
2763 // Cleanup memory
2764 for (int j = 0; j < i; j++) {
2765 SDL_free(charFileList[j]);
2766 }
2767 SDL_free(charFileList);
2768 return;
2769 }
2770
2771 charFileList[i] = newFile;
2772 (*env)->ReleaseStringUTFChars(env, string, utf8string);
2773 (*env)->DeleteLocalRef(env, string);
2774 }
2775
2776 // Call user-provided callback
2777 SDL_ClearError();
2778 mAndroidFileDialogData.callback(mAndroidFileDialogData.userdata, (const char *const *) charFileList, filter);
2779 mAndroidFileDialogData.callback = NULL;
2780
2781 // Cleanup memory
2782 for (int i = 0; i < count; i++) {
2783 SDL_free(charFileList[i]);
2784 }
2785 SDL_free(charFileList);
2786 }
2787}
2788
2789bool Android_JNI_OpenFileDialog(
2790 SDL_DialogFileCallback callback, void* userdata,
2791 const SDL_DialogFileFilter *filters, int nfilters, bool forwrite,
2792 bool multiple)
2793{
2794 if (mAndroidFileDialogData.callback != NULL) {
2795 SDL_SetError("Only one file dialog can be run at a time.");
2796 return false;
2797 }
2798
2799 if (forwrite) {
2800 multiple = false;
2801 }
2802
2803 JNIEnv *env = Android_JNI_GetEnv();
2804
2805 // Setup filters
2806 jobjectArray filtersArray = NULL;
2807 if (filters) {
2808 jclass stringClass = (*env)->FindClass(env, "java/lang/String");
2809 filtersArray = (*env)->NewObjectArray(env, nfilters, stringClass, NULL);
2810
2811 // Convert to string
2812 for (int i = 0; i < nfilters; i++) {
2813 jstring str = (*env)->NewStringUTF(env, filters[i].pattern);
2814 (*env)->SetObjectArrayElement(env, filtersArray, i, str);
2815 (*env)->DeleteLocalRef(env, str);
2816 }
2817 }
2818
2819 // Setup data
2820 static SDL_AtomicInt next_request_code;
2821 mAndroidFileDialogData.request_code = SDL_AddAtomicInt(&next_request_code, 1);
2822 mAndroidFileDialogData.userdata = userdata;
2823 mAndroidFileDialogData.callback = callback;
2824
2825 // Invoke JNI
2826 jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass,
2827 midShowFileDialog, filtersArray, (jboolean) multiple, (jboolean) forwrite, mAndroidFileDialogData.request_code);
2828 (*env)->DeleteLocalRef(env, filtersArray);
2829 if (!success) {
2830 mAndroidFileDialogData.callback = NULL;
2831 SDL_AddAtomicInt(&next_request_code, -1);
2832 SDL_SetError("Unspecified error in JNI");
2833
2834 return false;
2835 }
2836
2837 return true;
2838}
2839
2840#endif // SDL_PLATFORM_ANDROID