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