c40c6764f2bd44f61ebe8f9d9b61afe9a85325fc
[platform/upstream/SDL.git] / src / core / android / SDL_android.c
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 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 #include "SDL_stdinc.h"
23 #include "SDL_assert.h"
24 #include "SDL_hints.h"
25 #include "SDL_log.h"
26 #include "SDL_main.h"
27
28 #ifdef __ANDROID__
29
30 #include "SDL_system.h"
31 #include "SDL_android.h"
32
33 #include "keyinfotable.h"
34
35 #include "../../events/SDL_events_c.h"
36 #include "../../video/android/SDL_androidkeyboard.h"
37 #include "../../video/android/SDL_androidmouse.h"
38 #include "../../video/android/SDL_androidtouch.h"
39 #include "../../video/android/SDL_androidvideo.h"
40 #include "../../video/android/SDL_androidwindow.h"
41 #include "../../joystick/android/SDL_sysjoystick_c.h"
42 #include "../../haptic/android/SDL_syshaptic_c.h"
43
44 #include <android/log.h>
45 #include <pthread.h>
46 #include <sys/types.h>
47 #include <unistd.h>
48 #include <dlfcn.h>
49 /* #define LOG_TAG "SDL_android" */
50 /* #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) */
51 /* #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */
52 #define LOGI(...) do {} while (0)
53 #define LOGE(...) do {} while (0)
54
55
56 #define SDL_JAVA_PREFIX                                 org_libsdl_app
57 #define CONCAT1(prefix, class, function)                CONCAT2(prefix, class, function)
58 #define CONCAT2(prefix, class, function)                Java_ ## prefix ## _ ## class ## _ ## function
59 #define SDL_JAVA_INTERFACE(function)                    CONCAT1(SDL_JAVA_PREFIX, SDLActivity, function)
60 #define SDL_JAVA_AUDIO_INTERFACE(function)              CONCAT1(SDL_JAVA_PREFIX, SDLAudioManager, function)
61 #define SDL_JAVA_CONTROLLER_INTERFACE(function)         CONCAT1(SDL_JAVA_PREFIX, SDLControllerManager, function)
62 #define SDL_JAVA_INTERFACE_INPUT_CONNECTION(function)   CONCAT1(SDL_JAVA_PREFIX, SDLInputConnection, function)
63
64
65 /* Java class SDLActivity */
66 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(
67         JNIEnv* mEnv, jclass cls);
68
69 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(
70         JNIEnv* env, jclass cls,
71         jstring library, jstring function, jobject array);
72
73 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
74         JNIEnv* env, jclass jcls,
75         jstring filename);
76
77 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
78         JNIEnv* env, jclass jcls,
79         jint width, jint height, jint format, jfloat rate);
80
81 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(
82         JNIEnv* env, jclass jcls);
83
84 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(
85         JNIEnv* env, jclass jcls);
86
87 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
88         JNIEnv* env, jclass jcls,
89         jint keycode);
90
91 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
92         JNIEnv* env, jclass jcls,
93         jint keycode);
94
95 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
96         JNIEnv* env, jclass jcls);
97
98 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
99         JNIEnv* env, jclass jcls,
100         jint touch_device_id_in, jint pointer_finger_id_in,
101         jint action, jfloat x, jfloat y, jfloat p);
102
103 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
104         JNIEnv* env, jclass jcls,
105         jint button, jint action, jfloat x, jfloat y);
106
107 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
108         JNIEnv* env, jclass jcls,
109         jfloat x, jfloat y, jfloat z);
110
111 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
112         JNIEnv* env, jclass jcls);
113
114 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
115         JNIEnv* env, jclass cls);
116
117 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
118         JNIEnv* env, jclass cls);
119
120 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
121         JNIEnv* env, jclass cls);
122
123 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
124         JNIEnv* env, jclass cls);
125
126 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
127         JNIEnv* env, jclass cls,
128         jstring name);
129
130 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
131         JNIEnv* env, jclass cls,
132         jstring name, jstring value);
133
134 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeEnvironmentVariablesSet)(
135         JNIEnv* env, jclass cls);
136
137 /* Java class SDLInputConnection */
138 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
139         JNIEnv* env, jclass cls,
140         jstring text, jint newCursorPosition);
141
142 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
143         JNIEnv* env, jclass cls,
144         jstring text, jint newCursorPosition);
145
146 /* Java class SDLAudioManager */
147 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(
148         JNIEnv *env, jclass jcls);
149
150 /* Java class SDLControllerManager */
151 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(
152         JNIEnv *env, jclass jcls);
153
154 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
155         JNIEnv* env, jclass jcls,
156         jint device_id, jint keycode);
157
158 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
159         JNIEnv* env, jclass jcls,
160         jint device_id, jint keycode);
161
162 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
163         JNIEnv* env, jclass jcls,
164         jint device_id, jint axis, jfloat value);
165
166 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
167         JNIEnv* env, jclass jcls,
168         jint device_id, jint hat_id, jint x, jint y);
169
170 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
171         JNIEnv* env, jclass jcls,
172         jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
173         jint nbuttons, jint naxes, jint nhats, jint nballs);
174
175 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
176         JNIEnv* env, jclass jcls,
177         jint device_id);
178
179 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
180         JNIEnv* env, jclass jcls,
181         jint device_id, jstring device_name);
182
183 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
184         JNIEnv* env, jclass jcls,
185         jint device_id);
186
187
188
189 /* Uncomment this to log messages entering and exiting methods in this file */
190 /* #define DEBUG_JNI */
191
192 static void Android_JNI_ThreadDestroyed(void*);
193
194 /*******************************************************************************
195  This file links the Java side of Android with libsdl
196 *******************************************************************************/
197 #include <jni.h>
198
199
200 /*******************************************************************************
201                                Globals
202 *******************************************************************************/
203 static pthread_key_t mThreadKey;
204 static JavaVM* mJavaVM;
205
206 /* Main activity */
207 static jclass mActivityClass;
208
209 /* method signatures */
210 static jmethodID midGetNativeSurface;
211 static jmethodID midSetActivityTitle;
212 static jmethodID midSetWindowStyle;
213 static jmethodID midSetOrientation;
214 static jmethodID midGetContext;
215 static jmethodID midIsAndroidTV;
216 static jmethodID midInputGetInputDeviceIds;
217 static jmethodID midSendMessage;
218 static jmethodID midShowTextInput;
219 static jmethodID midIsScreenKeyboardShown;
220 static jmethodID midClipboardSetText;
221 static jmethodID midClipboardGetText;
222 static jmethodID midClipboardHasText;
223 static jmethodID midOpenAPKExpansionInputStream;
224 static jmethodID midGetManifestEnvironmentVariables;
225 static jmethodID midGetDisplayDPI;
226
227 /* audio manager */
228 static jclass mAudioManagerClass;
229
230 /* method signatures */
231 static jmethodID midAudioOpen;
232 static jmethodID midAudioWriteShortBuffer;
233 static jmethodID midAudioWriteByteBuffer;
234 static jmethodID midAudioClose;
235 static jmethodID midCaptureOpen;
236 static jmethodID midCaptureReadShortBuffer;
237 static jmethodID midCaptureReadByteBuffer;
238 static jmethodID midCaptureClose;
239
240 /* controller manager */
241 static jclass mControllerManagerClass;
242
243 /* method signatures */
244 static jmethodID midPollInputDevices;
245 static jmethodID midPollHapticDevices;
246 static jmethodID midHapticRun;
247
248 /* static fields */
249 static jfieldID fidSeparateMouseAndTouch;
250
251 /* Accelerometer data storage */
252 static float fLastAccelerometer[3];
253 static SDL_bool bHasNewData;
254
255 static SDL_bool bHasEnvironmentVariables = SDL_FALSE;
256
257 /*******************************************************************************
258                  Functions called by JNI
259 *******************************************************************************/
260
261 /* Library init */
262 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
263 {
264     JNIEnv *env;
265     mJavaVM = vm;
266     LOGI("JNI_OnLoad called");
267     if ((*mJavaVM)->GetEnv(mJavaVM, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
268         LOGE("Failed to get the environment using GetEnv()");
269         return -1;
270     }
271     /*
272      * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
273      * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
274      */
275     if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed) != 0) {
276         __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
277     }
278     Android_JNI_SetupThread();
279
280     return JNI_VERSION_1_4;
281 }
282
283 void checkJNIReady()
284 {
285     if (!mActivityClass || !mAudioManagerClass || !mControllerManagerClass) {
286         // We aren't fully initialized, let's just return.
287         return;
288     }
289
290     SDL_SetMainReady();    
291 }
292
293 /* Activity initialization -- called before SDL_main() to initialize JNI bindings */
294 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
295 {
296     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeSetupJNI()");
297
298     Android_JNI_SetupThread();
299
300     mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
301
302     midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
303                                 "getNativeSurface","()Landroid/view/Surface;");
304     midSetActivityTitle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
305                                 "setActivityTitle","(Ljava/lang/String;)Z");
306     midSetWindowStyle = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
307                                 "setWindowStyle","(Z)V");
308     midSetOrientation = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
309                                 "setOrientation","(IIZLjava/lang/String;)V");
310     midGetContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
311                                 "getContext","()Landroid/content/Context;");
312     midIsAndroidTV = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
313                                 "isAndroidTV","()Z");
314     midInputGetInputDeviceIds = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
315                                 "inputGetInputDeviceIds", "(I)[I");
316     midSendMessage = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
317                                 "sendMessage", "(II)Z");
318     midShowTextInput =  (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
319                                 "showTextInput", "(IIII)Z");
320     midIsScreenKeyboardShown = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
321                                 "isScreenKeyboardShown","()Z");
322     midClipboardSetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
323                                 "clipboardSetText", "(Ljava/lang/String;)V");
324     midClipboardGetText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
325                                 "clipboardGetText", "()Ljava/lang/String;");
326     midClipboardHasText = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
327                                 "clipboardHasText", "()Z");
328     midOpenAPKExpansionInputStream = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
329                                 "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
330
331     midGetManifestEnvironmentVariables = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
332                                 "getManifestEnvironmentVariables", "()Z");
333
334     midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
335     midGetDisplayDPI = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, "getDisplayDPI", "()Landroid/util/DisplayMetrics;");
336
337     if (!midGetNativeSurface ||
338        !midSetActivityTitle || !midSetWindowStyle || !midSetOrientation || !midGetContext || !midIsAndroidTV || !midInputGetInputDeviceIds ||
339        !midSendMessage || !midShowTextInput || !midIsScreenKeyboardShown ||
340        !midClipboardSetText || !midClipboardGetText || !midClipboardHasText ||
341        !midOpenAPKExpansionInputStream || !midGetManifestEnvironmentVariables|| !midGetDisplayDPI) {
342         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLActivity.java?");
343     }
344
345     fidSeparateMouseAndTouch = (*mEnv)->GetStaticFieldID(mEnv, mActivityClass, "mSeparateMouseAndTouch", "Z");
346
347     if (!fidSeparateMouseAndTouch) {
348         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java static fields, do you have the latest version of SDLActivity.java?");
349     }
350
351     checkJNIReady();
352 }
353
354 /* Audio initialization -- called before SDL_main() to initialize JNI bindings */
355 JNIEXPORT void JNICALL SDL_JAVA_AUDIO_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
356 {
357     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "AUDIO nativeSetupJNI()");
358
359     Android_JNI_SetupThread();
360
361     mAudioManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
362
363     midAudioOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
364                                 "audioOpen", "(IZZI)I");
365     midAudioWriteShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
366                                 "audioWriteShortBuffer", "([S)V");
367     midAudioWriteByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
368                                 "audioWriteByteBuffer", "([B)V");
369     midAudioClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
370                                 "audioClose", "()V");
371     midCaptureOpen = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
372                                 "captureOpen", "(IZZI)I");
373     midCaptureReadShortBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
374                                 "captureReadShortBuffer", "([SZ)I");
375     midCaptureReadByteBuffer = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
376                                 "captureReadByteBuffer", "([BZ)I");
377     midCaptureClose = (*mEnv)->GetStaticMethodID(mEnv, mAudioManagerClass,
378                                 "captureClose", "()V");
379
380     if (!midAudioOpen || !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioClose ||
381        !midCaptureOpen || !midCaptureReadShortBuffer || !midCaptureReadByteBuffer || !midCaptureClose) {
382         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLAudioManager.java?");
383     }
384
385     checkJNIReady();
386 }
387
388 /* Controller initialization -- called before SDL_main() to initialize JNI bindings */
389 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeSetupJNI)(JNIEnv* mEnv, jclass cls)
390 {
391     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "CONTROLLER nativeSetupJNI()");
392
393     Android_JNI_SetupThread();
394
395     mControllerManagerClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
396
397     midPollInputDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
398                                 "pollInputDevices", "()V");
399     midPollHapticDevices = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
400                                 "pollHapticDevices", "()V");
401     midHapticRun = (*mEnv)->GetStaticMethodID(mEnv, mControllerManagerClass,
402                                 "hapticRun", "(II)V");
403
404     if (!midPollInputDevices || !midPollHapticDevices || !midHapticRun) {
405         __android_log_print(ANDROID_LOG_WARN, "SDL", "Missing some Java callbacks, do you have the latest version of SDLControllerManager.java?");
406     }
407
408     checkJNIReady();
409 }
410
411 /* SDL main function prototype */
412 typedef int (*SDL_main_func)(int argc, char *argv[]);
413
414 /* Start up the SDL app */
415 JNIEXPORT int JNICALL SDL_JAVA_INTERFACE(nativeRunMain)(JNIEnv* env, jclass cls, jstring library, jstring function, jobject array)
416 {
417     int status = -1;
418     const char *library_file;
419     void *library_handle;
420
421     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeRunMain()");
422
423     library_file = (*env)->GetStringUTFChars(env, library, NULL);
424     library_handle = dlopen(library_file, RTLD_GLOBAL);
425     if (library_handle) {
426         const char *function_name;
427         SDL_main_func SDL_main;
428
429         function_name = (*env)->GetStringUTFChars(env, function, NULL);
430         SDL_main = (SDL_main_func)dlsym(library_handle, function_name);
431         if (SDL_main) {
432             int i;
433             int argc;
434             int len;
435             char **argv;
436
437             /* Prepare the arguments. */
438             len = (*env)->GetArrayLength(env, array);
439             argv = SDL_stack_alloc(char*, 1 + len + 1);
440             argc = 0;
441             /* Use the name "app_process" so PHYSFS_platformCalcBaseDir() works.
442                https://bitbucket.org/MartinFelis/love-android-sdl2/issue/23/release-build-crash-on-start
443              */
444             argv[argc++] = SDL_strdup("app_process");
445             for (i = 0; i < len; ++i) {
446                 const char* utf;
447                 char* arg = NULL;
448                 jstring string = (*env)->GetObjectArrayElement(env, array, i);
449                 if (string) {
450                     utf = (*env)->GetStringUTFChars(env, string, 0);
451                     if (utf) {
452                         arg = SDL_strdup(utf);
453                         (*env)->ReleaseStringUTFChars(env, string, utf);
454                     }
455                     (*env)->DeleteLocalRef(env, string);
456                 }
457                 if (!arg) {
458                     arg = SDL_strdup("");
459                 }
460                 argv[argc++] = arg;
461             }
462             argv[argc] = NULL;
463
464
465             /* Run the application. */
466             status = SDL_main(argc, argv);
467
468             /* Release the arguments. */
469             for (i = 0; i < argc; ++i) {
470                 SDL_free(argv[i]);
471             }
472             SDL_stack_free(argv);
473
474         } else {
475             __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't find function %s in library %s", function_name, library_file);
476         }
477         (*env)->ReleaseStringUTFChars(env, function, function_name);
478
479         dlclose(library_handle);
480
481     } else {
482         __android_log_print(ANDROID_LOG_ERROR, "SDL", "nativeRunMain(): Couldn't load library %s", library_file);
483     }
484     (*env)->ReleaseStringUTFChars(env, library, library_file);
485
486     /* Do not issue an exit or the whole application will terminate instead of just the SDL thread */
487     /* exit(status); */
488
489     return status;
490 }
491
492 /* Drop file */
493 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeDropFile)(
494                                     JNIEnv* env, jclass jcls,
495                                     jstring filename)
496 {
497     const char *path = (*env)->GetStringUTFChars(env, filename, NULL);
498     SDL_SendDropFile(NULL, path);
499     (*env)->ReleaseStringUTFChars(env, filename, path);
500     SDL_SendDropComplete(NULL);
501 }
502
503 /* Resize */
504 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeResize)(
505                                     JNIEnv* env, jclass jcls,
506                                     jint width, jint height, jint format, jfloat rate)
507 {
508     Android_SetScreenResolution(width, height, format, rate);
509 }
510
511 /* Paddown */
512 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadDown)(
513                                     JNIEnv* env, jclass jcls,
514                                     jint device_id, jint keycode)
515 {
516     return Android_OnPadDown(device_id, keycode);
517 }
518
519 /* Padup */
520 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativePadUp)(
521                                     JNIEnv* env, jclass jcls,
522                                     jint device_id, jint keycode)
523 {
524     return Android_OnPadUp(device_id, keycode);
525 }
526
527 /* Joy */
528 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeJoy)(
529                                     JNIEnv* env, jclass jcls,
530                                     jint device_id, jint axis, jfloat value)
531 {
532     Android_OnJoy(device_id, axis, value);
533 }
534
535 /* POV Hat */
536 JNIEXPORT void JNICALL SDL_JAVA_CONTROLLER_INTERFACE(onNativeHat)(
537                                     JNIEnv* env, jclass jcls,
538                                     jint device_id, jint hat_id, jint x, jint y)
539 {
540     Android_OnHat(device_id, hat_id, x, y);
541 }
542
543
544 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddJoystick)(
545                                     JNIEnv* env, jclass jcls,
546                                     jint device_id, jstring device_name, jstring device_desc, jint is_accelerometer,
547                                     jint nbuttons, jint naxes, jint nhats, jint nballs)
548 {
549     int retval;
550     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
551     const char *desc = (*env)->GetStringUTFChars(env, device_desc, NULL);
552
553     retval = Android_AddJoystick(device_id, name, desc, (SDL_bool) is_accelerometer, nbuttons, naxes, nhats, nballs);
554
555     (*env)->ReleaseStringUTFChars(env, device_name, name);
556     (*env)->ReleaseStringUTFChars(env, device_desc, desc);
557
558     return retval;
559 }
560
561 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveJoystick)(
562                                     JNIEnv* env, jclass jcls,
563                                     jint device_id)
564 {
565     return Android_RemoveJoystick(device_id);
566 }
567
568 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeAddHaptic)(
569     JNIEnv* env, jclass jcls, jint device_id, jstring device_name)
570 {
571     int retval;
572     const char *name = (*env)->GetStringUTFChars(env, device_name, NULL);
573
574     retval = Android_AddHaptic(device_id, name);
575
576     (*env)->ReleaseStringUTFChars(env, device_name, name);
577
578     return retval;
579 }
580
581 JNIEXPORT jint JNICALL SDL_JAVA_CONTROLLER_INTERFACE(nativeRemoveHaptic)(
582     JNIEnv* env, jclass jcls, jint device_id)
583 {
584     return Android_RemoveHaptic(device_id);
585 }
586
587
588 /* Surface Created */
589 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceChanged)(JNIEnv* env, jclass jcls)
590 {
591     SDL_WindowData *data;
592     SDL_VideoDevice *_this;
593
594     if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
595         return;
596     }
597
598     _this =  SDL_GetVideoDevice();
599     data =  (SDL_WindowData *) Android_Window->driverdata;
600
601     /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
602     if (data->egl_surface == EGL_NO_SURFACE) {
603         if(data->native_window) {
604             ANativeWindow_release(data->native_window);
605         }
606         data->native_window = Android_JNI_GetNativeWindow();
607         data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
608     }
609
610     /* GL Context handling is done in the event loop because this function is run from the Java thread */
611
612 }
613
614 /* Surface Destroyed */
615 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeSurfaceDestroyed)(JNIEnv* env, jclass jcls)
616 {
617     /* We have to clear the current context and destroy the egl surface here
618      * Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
619      * Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
620      */
621     SDL_WindowData *data;
622     SDL_VideoDevice *_this;
623
624     if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
625         return;
626     }
627
628     _this =  SDL_GetVideoDevice();
629     data = (SDL_WindowData *) Android_Window->driverdata;
630
631     if (data->egl_surface != EGL_NO_SURFACE) {
632         SDL_EGL_MakeCurrent(_this, NULL, NULL);
633         SDL_EGL_DestroySurface(_this, data->egl_surface);
634         data->egl_surface = EGL_NO_SURFACE;
635     }
636
637     /* GL Context handling is done in the event loop because this function is run from the Java thread */
638
639 }
640
641 /* Keydown */
642 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyDown)(
643                                     JNIEnv* env, jclass jcls,
644                                     jint keycode)
645 {
646     Android_OnKeyDown(keycode);
647 }
648
649 /* Keyup */
650 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyUp)(
651                                     JNIEnv* env, jclass jcls,
652                                     jint keycode)
653 {
654     Android_OnKeyUp(keycode);
655 }
656
657 /* Keyboard Focus Lost */
658 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeKeyboardFocusLost)(
659                                     JNIEnv* env, jclass jcls)
660 {
661     /* Calling SDL_StopTextInput will take care of hiding the keyboard and cleaning up the DummyText widget */
662     SDL_StopTextInput();
663 }
664
665
666 /* Touch */
667 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeTouch)(
668                                     JNIEnv* env, jclass jcls,
669                                     jint touch_device_id_in, jint pointer_finger_id_in,
670                                     jint action, jfloat x, jfloat y, jfloat p)
671 {
672     Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
673 }
674
675 /* Mouse */
676 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeMouse)(
677                                     JNIEnv* env, jclass jcls,
678                                     jint button, jint action, jfloat x, jfloat y)
679 {
680     Android_OnMouse(button, action, x, y);
681 }
682
683 /* Accelerometer */
684 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeAccel)(
685                                     JNIEnv* env, jclass jcls,
686                                     jfloat x, jfloat y, jfloat z)
687 {
688     fLastAccelerometer[0] = x;
689     fLastAccelerometer[1] = y;
690     fLastAccelerometer[2] = z;
691     bHasNewData = SDL_TRUE;
692 }
693
694 /* Clipboard */
695 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(onNativeClipboardChanged)(
696                                     JNIEnv* env, jclass jcls)
697 {
698     SDL_SendClipboardUpdate();
699 }
700
701 /* Low memory */
702 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeLowMemory)(
703                                     JNIEnv* env, jclass cls)
704 {
705     SDL_SendAppEvent(SDL_APP_LOWMEMORY);
706 }
707
708 /* Quit */
709 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeQuit)(
710                                     JNIEnv* env, jclass cls)
711 {
712     /* Discard previous events. The user should have handled state storage
713      * in SDL_APP_WILLENTERBACKGROUND. After nativeQuit() is called, no
714      * events other than SDL_QUIT and SDL_APP_TERMINATING should fire */
715     SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
716     /* Inject a SDL_QUIT event */
717     SDL_SendQuit();
718     SDL_SendAppEvent(SDL_APP_TERMINATING);
719     /* Resume the event loop so that the app can catch SDL_QUIT which
720      * should now be the top event in the event queue. */
721     if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
722 }
723
724 /* Pause */
725 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativePause)(
726                                     JNIEnv* env, jclass cls)
727 {
728     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
729
730     if (Android_Window) {
731         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_LOST, 0, 0);
732         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_MINIMIZED, 0, 0);
733         SDL_SendAppEvent(SDL_APP_WILLENTERBACKGROUND);
734         SDL_SendAppEvent(SDL_APP_DIDENTERBACKGROUND);
735
736         /* *After* sending the relevant events, signal the pause semaphore
737          * so the event loop knows to pause and (optionally) block itself */
738         if (!SDL_SemValue(Android_PauseSem)) SDL_SemPost(Android_PauseSem);
739     }
740 }
741
742 /* Resume */
743 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeResume)(
744                                     JNIEnv* env, jclass cls)
745 {
746     __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
747
748     if (Android_Window) {
749         SDL_SendAppEvent(SDL_APP_WILLENTERFOREGROUND);
750         SDL_SendAppEvent(SDL_APP_DIDENTERFOREGROUND);
751         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_FOCUS_GAINED, 0, 0);
752         SDL_SendWindowEvent(Android_Window, SDL_WINDOWEVENT_RESTORED, 0, 0);
753         /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
754          * We can't restore the GL Context here because it needs to be done on the SDL main thread
755          * and this function will be called from the Java thread instead.
756          */
757         if (!SDL_SemValue(Android_ResumeSem)) SDL_SemPost(Android_ResumeSem);
758     }
759 }
760
761 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeCommitText)(
762                                     JNIEnv* env, jclass cls,
763                                     jstring text, jint newCursorPosition)
764 {
765     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
766
767     SDL_SendKeyboardText(utftext);
768
769     (*env)->ReleaseStringUTFChars(env, text, utftext);
770 }
771
772 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeGenerateScancodeForUnichar)(
773                                     JNIEnv* env, jclass cls,
774                                     jchar chUnicode)
775 {
776     SDL_Scancode code = SDL_SCANCODE_UNKNOWN;
777     uint16_t mod = 0;
778
779     // We do not care about bigger than 127.
780     if (chUnicode < 127) {
781         AndroidKeyInfo info = unicharToAndroidKeyInfoTable[chUnicode];
782         code = info.code;
783         mod = info.mod;
784     }
785
786     if (mod & KMOD_SHIFT) {
787         /* If character uses shift, press shift down */
788         SDL_SendKeyboardKey(SDL_PRESSED, SDL_SCANCODE_LSHIFT);
789     }
790
791     /* send a keydown and keyup even for the character */
792     SDL_SendKeyboardKey(SDL_PRESSED, code);
793     SDL_SendKeyboardKey(SDL_RELEASED, code);
794
795     if (mod & KMOD_SHIFT) {
796         /* If character uses shift, press shift back up */
797         SDL_SendKeyboardKey(SDL_RELEASED, SDL_SCANCODE_LSHIFT);
798     }
799 }
800
801
802 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE_INPUT_CONNECTION(nativeSetComposingText)(
803                                     JNIEnv* env, jclass cls,
804                                     jstring text, jint newCursorPosition)
805 {
806     const char *utftext = (*env)->GetStringUTFChars(env, text, NULL);
807
808     SDL_SendEditingText(utftext, 0, 0);
809
810     (*env)->ReleaseStringUTFChars(env, text, utftext);
811 }
812
813 JNIEXPORT jstring JNICALL SDL_JAVA_INTERFACE(nativeGetHint)(
814                                     JNIEnv* env, jclass cls,
815                                     jstring name)
816 {
817     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
818     const char *hint = SDL_GetHint(utfname);
819
820     jstring result = (*env)->NewStringUTF(env, hint);
821     (*env)->ReleaseStringUTFChars(env, name, utfname);
822
823     return result;
824 }
825
826 JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
827                                     JNIEnv* env, jclass cls,
828                                     jstring name, jstring value)
829 {
830     const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
831     const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
832
833     SDL_setenv(utfname, utfvalue, 1);
834
835     (*env)->ReleaseStringUTFChars(env, name, utfname);
836     (*env)->ReleaseStringUTFChars(env, value, utfvalue);
837
838 }
839
840 /*******************************************************************************
841              Functions called by SDL into Java
842 *******************************************************************************/
843
844 static int s_active = 0;
845 struct LocalReferenceHolder
846 {
847     JNIEnv *m_env;
848     const char *m_func;
849 };
850
851 static struct LocalReferenceHolder LocalReferenceHolder_Setup(const char *func)
852 {
853     struct LocalReferenceHolder refholder;
854     refholder.m_env = NULL;
855     refholder.m_func = func;
856 #ifdef DEBUG_JNI
857     SDL_Log("Entering function %s", func);
858 #endif
859     return refholder;
860 }
861
862 static SDL_bool LocalReferenceHolder_Init(struct LocalReferenceHolder *refholder, JNIEnv *env)
863 {
864     const int capacity = 16;
865     if ((*env)->PushLocalFrame(env, capacity) < 0) {
866         SDL_SetError("Failed to allocate enough JVM local references");
867         return SDL_FALSE;
868     }
869     ++s_active;
870     refholder->m_env = env;
871     return SDL_TRUE;
872 }
873
874 static void LocalReferenceHolder_Cleanup(struct LocalReferenceHolder *refholder)
875 {
876 #ifdef DEBUG_JNI
877     SDL_Log("Leaving function %s", refholder->m_func);
878 #endif
879     if (refholder->m_env) {
880         JNIEnv* env = refholder->m_env;
881         (*env)->PopLocalFrame(env, NULL);
882         --s_active;
883     }
884 }
885
886 static SDL_bool LocalReferenceHolder_IsActive(void)
887 {
888     return s_active > 0;
889 }
890
891 ANativeWindow* Android_JNI_GetNativeWindow(void)
892 {
893     ANativeWindow* anw;
894     jobject s;
895     JNIEnv *env = Android_JNI_GetEnv();
896
897     s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
898     anw = ANativeWindow_fromSurface(env, s);
899     (*env)->DeleteLocalRef(env, s);
900
901     return anw;
902 }
903
904 void Android_JNI_SetActivityTitle(const char *title)
905 {
906     JNIEnv *mEnv = Android_JNI_GetEnv();
907
908     jstring jtitle = (jstring)((*mEnv)->NewStringUTF(mEnv, title));
909     (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midSetActivityTitle, jtitle);
910     (*mEnv)->DeleteLocalRef(mEnv, jtitle);
911 }
912
913 void Android_JNI_SetWindowStyle(SDL_bool fullscreen)
914 {
915     JNIEnv *mEnv = Android_JNI_GetEnv();
916     (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetWindowStyle, fullscreen ? 1 : 0);
917 }
918
919 void Android_JNI_SetOrientation(int w, int h, int resizable, const char *hint)
920 {
921     JNIEnv *mEnv = Android_JNI_GetEnv();
922
923     jstring jhint = (jstring)((*mEnv)->NewStringUTF(mEnv, (hint ? hint : "")));
924     (*mEnv)->CallStaticVoidMethod(mEnv, mActivityClass, midSetOrientation, w, h, (resizable? 1 : 0), jhint);
925     (*mEnv)->DeleteLocalRef(mEnv, jhint);
926 }
927
928 SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
929 {
930     int i;
931     SDL_bool retval = SDL_FALSE;
932
933     if (bHasNewData) {
934         for (i = 0; i < 3; ++i) {
935             values[i] = fLastAccelerometer[i];
936         }
937         bHasNewData = SDL_FALSE;
938         retval = SDL_TRUE;
939     }
940
941     return retval;
942 }
943
944 static void Android_JNI_ThreadDestroyed(void* value)
945 {
946     /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
947     JNIEnv *env = (JNIEnv*) value;
948     if (env != NULL) {
949         (*mJavaVM)->DetachCurrentThread(mJavaVM);
950         pthread_setspecific(mThreadKey, NULL);
951     }
952 }
953
954 JNIEnv* Android_JNI_GetEnv(void)
955 {
956     /* From http://developer.android.com/guide/practices/jni.html
957      * All threads are Linux threads, scheduled by the kernel.
958      * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
959      * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
960      * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
961      * and cannot make JNI calls.
962      * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
963      * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
964      * is a no-op.
965      * Note: You can call this function any number of times for the same thread, there's no harm in it
966      */
967
968     JNIEnv *env;
969     int status = (*mJavaVM)->AttachCurrentThread(mJavaVM, &env, NULL);
970     if(status < 0) {
971         LOGE("failed to attach current thread");
972         return 0;
973     }
974
975     /* From http://developer.android.com/guide/practices/jni.html
976      * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
977      * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
978      * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
979      * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
980      * Note: The destructor is not called unless the stored value is != NULL
981      * Note: You can call this function any number of times for the same thread, there's no harm in it
982      *       (except for some lost CPU cycles)
983      */
984     pthread_setspecific(mThreadKey, (void*) env);
985
986     return env;
987 }
988
989 int Android_JNI_SetupThread(void)
990 {
991     Android_JNI_GetEnv();
992     return 1;
993 }
994
995 /*
996  * Audio support
997  */
998 static jboolean audioBuffer16Bit = JNI_FALSE;
999 static jobject audioBuffer = NULL;
1000 static void* audioBufferPinned = NULL;
1001 static jboolean captureBuffer16Bit = JNI_FALSE;
1002 static jobject captureBuffer = NULL;
1003
1004 int Android_JNI_OpenAudioDevice(int iscapture, int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
1005 {
1006     jboolean audioBufferStereo;
1007     int audioBufferFrames;
1008     jobject jbufobj = NULL;
1009     jboolean isCopy;
1010
1011     JNIEnv *env = Android_JNI_GetEnv();
1012
1013     if (!env) {
1014         LOGE("callback_handler: failed to attach current thread");
1015     }
1016     Android_JNI_SetupThread();
1017
1018     audioBufferStereo = channelCount > 1;
1019
1020     if (iscapture) {
1021         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for capture");
1022         captureBuffer16Bit = is16Bit;
1023         if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1024             /* Error during audio initialization */
1025             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioRecord initialization!");
1026             return 0;
1027         }
1028     } else {
1029         __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device for output");
1030         audioBuffer16Bit = is16Bit;
1031         if ((*env)->CallStaticIntMethod(env, mAudioManagerClass, midAudioOpen, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames) != 0) {
1032             /* Error during audio initialization */
1033             __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: error on AudioTrack initialization!");
1034             return 0;
1035         }
1036     }
1037
1038     /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
1039      * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
1040
1041     if (is16Bit) {
1042         jshortArray audioBufferLocal = (*env)->NewShortArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1043         if (audioBufferLocal) {
1044             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1045             (*env)->DeleteLocalRef(env, audioBufferLocal);
1046         }
1047     }
1048     else {
1049         jbyteArray audioBufferLocal = (*env)->NewByteArray(env, desiredBufferFrames * (audioBufferStereo ? 2 : 1));
1050         if (audioBufferLocal) {
1051             jbufobj = (*env)->NewGlobalRef(env, audioBufferLocal);
1052             (*env)->DeleteLocalRef(env, audioBufferLocal);
1053         }
1054     }
1055
1056     if (jbufobj == NULL) {
1057         __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
1058         return 0;
1059     }
1060
1061     if (iscapture) {
1062         captureBuffer = jbufobj;
1063     } else {
1064         audioBuffer = jbufobj;
1065     }
1066
1067     isCopy = JNI_FALSE;
1068
1069     if (is16Bit) {
1070         if (!iscapture) {
1071             audioBufferPinned = (*env)->GetShortArrayElements(env, (jshortArray)audioBuffer, &isCopy);
1072         }
1073         audioBufferFrames = (*env)->GetArrayLength(env, (jshortArray)audioBuffer);
1074     } else {
1075         if (!iscapture) {
1076             audioBufferPinned = (*env)->GetByteArrayElements(env, (jbyteArray)audioBuffer, &isCopy);
1077         }
1078         audioBufferFrames = (*env)->GetArrayLength(env, (jbyteArray)audioBuffer);
1079     }
1080
1081     if (audioBufferStereo) {
1082         audioBufferFrames /= 2;
1083     }
1084
1085     return audioBufferFrames;
1086 }
1087
1088 int Android_JNI_GetDisplayDPI(float *ddpi, float *xdpi, float *ydpi)
1089 {
1090     JNIEnv *env = Android_JNI_GetEnv();
1091
1092     jobject jDisplayObj = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetDisplayDPI);
1093     jclass jDisplayClass = (*env)->GetObjectClass(env, jDisplayObj);
1094
1095     jfieldID fidXdpi = (*env)->GetFieldID(env, jDisplayClass, "xdpi", "F");
1096     jfieldID fidYdpi = (*env)->GetFieldID(env, jDisplayClass, "ydpi", "F");
1097     jfieldID fidDdpi = (*env)->GetFieldID(env, jDisplayClass, "densityDpi", "I");
1098
1099     float nativeXdpi = (*env)->GetFloatField(env, jDisplayObj, fidXdpi);
1100     float nativeYdpi = (*env)->GetFloatField(env, jDisplayObj, fidYdpi);
1101     int nativeDdpi = (*env)->GetIntField(env, jDisplayObj, fidDdpi);
1102
1103
1104     (*env)->DeleteLocalRef(env, jDisplayObj);
1105     (*env)->DeleteLocalRef(env, jDisplayClass);
1106
1107     if (ddpi) {
1108         *ddpi = (float)nativeDdpi;
1109     }
1110     if (xdpi) {
1111         *xdpi = nativeXdpi;
1112     }
1113     if (ydpi) {
1114         *ydpi = nativeYdpi;
1115     }
1116
1117     return 0;
1118 }
1119
1120 void * Android_JNI_GetAudioBuffer(void)
1121 {
1122     return audioBufferPinned;
1123 }
1124
1125 void Android_JNI_WriteAudioBuffer(void)
1126 {
1127     JNIEnv *mAudioEnv = Android_JNI_GetEnv();
1128
1129     if (audioBuffer16Bit) {
1130         (*mAudioEnv)->ReleaseShortArrayElements(mAudioEnv, (jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
1131         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
1132     } else {
1133         (*mAudioEnv)->ReleaseByteArrayElements(mAudioEnv, (jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
1134         (*mAudioEnv)->CallStaticVoidMethod(mAudioEnv, mAudioManagerClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
1135     }
1136
1137     /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
1138 }
1139
1140 int Android_JNI_CaptureAudioBuffer(void *buffer, int buflen)
1141 {
1142     JNIEnv *env = Android_JNI_GetEnv();
1143     jboolean isCopy = JNI_FALSE;
1144     jint br;
1145
1146     if (captureBuffer16Bit) {
1147         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == (buflen / 2));
1148         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_TRUE);
1149         if (br > 0) {
1150             jshort *ptr = (*env)->GetShortArrayElements(env, (jshortArray)captureBuffer, &isCopy);
1151             br *= 2;
1152             SDL_memcpy(buffer, ptr, br);
1153             (*env)->ReleaseShortArrayElements(env, (jshortArray)captureBuffer, (jshort *)ptr, JNI_ABORT);
1154         }
1155     } else {
1156         SDL_assert((*env)->GetArrayLength(env, (jshortArray)captureBuffer) == buflen);
1157         br = (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_TRUE);
1158         if (br > 0) {
1159             jbyte *ptr = (*env)->GetByteArrayElements(env, (jbyteArray)captureBuffer, &isCopy);
1160             SDL_memcpy(buffer, ptr, br);
1161             (*env)->ReleaseByteArrayElements(env, (jbyteArray)captureBuffer, (jbyte *)ptr, JNI_ABORT);
1162         }
1163     }
1164
1165     return (int) br;
1166 }
1167
1168 void Android_JNI_FlushCapturedAudio(void)
1169 {
1170     JNIEnv *env = Android_JNI_GetEnv();
1171 #if 0  /* !!! FIXME: this needs API 23, or it'll do blocking reads and never end. */
1172     if (captureBuffer16Bit) {
1173         const jint len = (*env)->GetArrayLength(env, (jshortArray)captureBuffer);
1174         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1175     } else {
1176         const jint len = (*env)->GetArrayLength(env, (jbyteArray)captureBuffer);
1177         while ((*env)->CallStaticIntMethod(env, mActivityClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE) == len) { /* spin */ }
1178     }
1179 #else
1180     if (captureBuffer16Bit) {
1181         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadShortBuffer, (jshortArray)captureBuffer, JNI_FALSE);
1182     } else {
1183         (*env)->CallStaticIntMethod(env, mAudioManagerClass, midCaptureReadByteBuffer, (jbyteArray)captureBuffer, JNI_FALSE);
1184     }
1185 #endif
1186 }
1187
1188 void Android_JNI_CloseAudioDevice(const int iscapture)
1189 {
1190     JNIEnv *env = Android_JNI_GetEnv();
1191
1192     if (iscapture) {
1193         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midCaptureClose);
1194         if (captureBuffer) {
1195             (*env)->DeleteGlobalRef(env, captureBuffer);
1196             captureBuffer = NULL;
1197         }
1198     } else {
1199         (*env)->CallStaticVoidMethod(env, mAudioManagerClass, midAudioClose);
1200         if (audioBuffer) {
1201             (*env)->DeleteGlobalRef(env, audioBuffer);
1202             audioBuffer = NULL;
1203             audioBufferPinned = NULL;
1204         }
1205     }
1206 }
1207
1208 /* Test for an exception and call SDL_SetError with its detail if one occurs */
1209 /* If the parameter silent is truthy then SDL_SetError() will not be called. */
1210 static SDL_bool Android_JNI_ExceptionOccurred(SDL_bool silent)
1211 {
1212     JNIEnv *mEnv = Android_JNI_GetEnv();
1213     jthrowable exception;
1214
1215     SDL_assert(LocalReferenceHolder_IsActive());
1216
1217     exception = (*mEnv)->ExceptionOccurred(mEnv);
1218     if (exception != NULL) {
1219         jmethodID mid;
1220
1221         /* Until this happens most JNI operations have undefined behaviour */
1222         (*mEnv)->ExceptionClear(mEnv);
1223
1224         if (!silent) {
1225             jclass exceptionClass = (*mEnv)->GetObjectClass(mEnv, exception);
1226             jclass classClass = (*mEnv)->FindClass(mEnv, "java/lang/Class");
1227             jstring exceptionName;
1228             const char* exceptionNameUTF8;
1229             jstring exceptionMessage;
1230
1231             mid = (*mEnv)->GetMethodID(mEnv, classClass, "getName", "()Ljava/lang/String;");
1232             exceptionName = (jstring)(*mEnv)->CallObjectMethod(mEnv, exceptionClass, mid);
1233             exceptionNameUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionName, 0);
1234
1235             mid = (*mEnv)->GetMethodID(mEnv, exceptionClass, "getMessage", "()Ljava/lang/String;");
1236             exceptionMessage = (jstring)(*mEnv)->CallObjectMethod(mEnv, exception, mid);
1237
1238             if (exceptionMessage != NULL) {
1239                 const char* exceptionMessageUTF8 = (*mEnv)->GetStringUTFChars(mEnv, exceptionMessage, 0);
1240                 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
1241                 (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionMessage, exceptionMessageUTF8);
1242             } else {
1243                 SDL_SetError("%s", exceptionNameUTF8);
1244             }
1245
1246             (*mEnv)->ReleaseStringUTFChars(mEnv, exceptionName, exceptionNameUTF8);
1247         }
1248
1249         return SDL_TRUE;
1250     }
1251
1252     return SDL_FALSE;
1253 }
1254
1255 static int Internal_Android_JNI_FileOpen(SDL_RWops* ctx)
1256 {
1257     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1258
1259     int result = 0;
1260
1261     jmethodID mid;
1262     jobject context;
1263     jobject assetManager;
1264     jobject inputStream;
1265     jclass channels;
1266     jobject readableByteChannel;
1267     jstring fileNameJString;
1268     jobject fd;
1269     jclass fdCls;
1270     jfieldID descriptor;
1271
1272     JNIEnv *mEnv = Android_JNI_GetEnv();
1273     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1274         goto failure;
1275     }
1276
1277     fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
1278     ctx->hidden.androidio.position = 0;
1279
1280     /* context = SDLActivity.getContext(); */
1281     context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midGetContext);
1282
1283     /* assetManager = context.getAssets(); */
1284     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
1285             "getAssets", "()Landroid/content/res/AssetManager;");
1286     assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
1287
1288     /* First let's try opening the file to obtain an AssetFileDescriptor.
1289     * This method reads the files directly from the APKs using standard *nix calls
1290     */
1291     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
1292     inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString);
1293     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1294         goto fallback;
1295     }
1296
1297     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getStartOffset", "()J");
1298     ctx->hidden.androidio.offset = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1299     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1300         goto fallback;
1301     }
1302
1303     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getDeclaredLength", "()J");
1304     ctx->hidden.androidio.size = (*mEnv)->CallLongMethod(mEnv, inputStream, mid);
1305     if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
1306         goto fallback;
1307     }
1308
1309     mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
1310     fd = (*mEnv)->CallObjectMethod(mEnv, inputStream, mid);
1311     fdCls = (*mEnv)->GetObjectClass(mEnv, fd);
1312     descriptor = (*mEnv)->GetFieldID(mEnv, fdCls, "descriptor", "I");
1313     ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
1314     ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1315
1316     /* Seek to the correct offset in the file. */
1317     lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
1318
1319     if (0) {
1320 fallback:
1321         /* Disabled log message because of spam on the Nexus 7 */
1322         /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
1323
1324         /* Try the old method using InputStream */
1325         ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1326
1327         /* inputStream = assetManager.open(<filename>); */
1328         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
1329                 "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
1330         inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
1331         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1332             /* Try fallback to APK expansion files */
1333             inputStream = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
1334
1335             /* Exception is checked first because it always needs to be cleared.
1336              * If no exception occurred then the last SDL error message is kept.
1337              */
1338             if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
1339                 goto failure;
1340             }
1341         }
1342
1343         ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
1344
1345         /* Despite all the visible documentation on [Asset]InputStream claiming
1346          * that the .available() method is not guaranteed to return the entire file
1347          * size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
1348          * android/apis/content/ReadAsset.java imply that Android's
1349          * AssetInputStream.available() /will/ always return the total file size
1350         */
1351
1352         /* size = inputStream.available(); */
1353         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1354                 "available", "()I");
1355         ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
1356         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1357             goto failure;
1358         }
1359
1360         /* readableByteChannel = Channels.newChannel(inputStream); */
1361         channels = (*mEnv)->FindClass(mEnv, "java/nio/channels/Channels");
1362         mid = (*mEnv)->GetStaticMethodID(mEnv, channels,
1363                 "newChannel",
1364                 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
1365         readableByteChannel = (*mEnv)->CallStaticObjectMethod(
1366                 mEnv, channels, mid, inputStream);
1367         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1368             goto failure;
1369         }
1370
1371         ctx->hidden.androidio.readableByteChannelRef =
1372             (*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
1373
1374         /* Store .read id for reading purposes */
1375         mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
1376                 "read", "(Ljava/nio/ByteBuffer;)I");
1377         ctx->hidden.androidio.readMethod = mid;
1378     }
1379
1380     if (0) {
1381 failure:
1382         result = -1;
1383
1384         (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1385
1386         if(ctx->hidden.androidio.inputStreamRef != NULL) {
1387             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1388         }
1389
1390         if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
1391             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1392         }
1393
1394         if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
1395             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1396         }
1397
1398     }
1399
1400     LocalReferenceHolder_Cleanup(&refs);
1401     return result;
1402 }
1403
1404 int Android_JNI_FileOpen(SDL_RWops* ctx,
1405         const char* fileName, const char* mode)
1406 {
1407     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1408     JNIEnv *mEnv = Android_JNI_GetEnv();
1409     int retval;
1410     jstring fileNameJString;
1411
1412     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1413         LocalReferenceHolder_Cleanup(&refs);
1414         return -1;
1415     }
1416
1417     if (!ctx) {
1418         LocalReferenceHolder_Cleanup(&refs);
1419         return -1;
1420     }
1421
1422     fileNameJString = (*mEnv)->NewStringUTF(mEnv, fileName);
1423     ctx->hidden.androidio.fileNameRef = (*mEnv)->NewGlobalRef(mEnv, fileNameJString);
1424     ctx->hidden.androidio.inputStreamRef = NULL;
1425     ctx->hidden.androidio.readableByteChannelRef = NULL;
1426     ctx->hidden.androidio.readMethod = NULL;
1427     ctx->hidden.androidio.assetFileDescriptorRef = NULL;
1428
1429     retval = Internal_Android_JNI_FileOpen(ctx);
1430     LocalReferenceHolder_Cleanup(&refs);
1431     return retval;
1432 }
1433
1434 size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
1435         size_t size, size_t maxnum)
1436 {
1437     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1438
1439     if (ctx->hidden.androidio.assetFileDescriptorRef) {
1440         size_t bytesMax = size * maxnum;
1441         size_t result;
1442         if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
1443             bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
1444         }
1445         result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
1446         if (result > 0) {
1447             ctx->hidden.androidio.position += result;
1448             LocalReferenceHolder_Cleanup(&refs);
1449             return result / size;
1450         }
1451         LocalReferenceHolder_Cleanup(&refs);
1452         return 0;
1453     } else {
1454         jlong bytesRemaining = (jlong) (size * maxnum);
1455         jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
1456         int bytesRead = 0;
1457         JNIEnv *mEnv;
1458         jobject readableByteChannel;
1459         jmethodID readMethod;
1460         jobject byteBuffer;
1461
1462         /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
1463         if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
1464
1465         mEnv = Android_JNI_GetEnv();
1466         if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1467             LocalReferenceHolder_Cleanup(&refs);
1468             return 0;
1469         }
1470
1471         readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
1472         readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
1473         byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
1474
1475         while (bytesRemaining > 0) {
1476             /* result = readableByteChannel.read(...); */
1477             int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
1478
1479             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1480                 LocalReferenceHolder_Cleanup(&refs);
1481                 return 0;
1482             }
1483
1484             if (result < 0) {
1485                 break;
1486             }
1487
1488             bytesRemaining -= result;
1489             bytesRead += result;
1490             ctx->hidden.androidio.position += result;
1491         }
1492         LocalReferenceHolder_Cleanup(&refs);
1493         return bytesRead / size;
1494     }
1495 }
1496
1497 size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
1498         size_t size, size_t num)
1499 {
1500     SDL_SetError("Cannot write to Android package filesystem");
1501     return 0;
1502 }
1503
1504 static int Internal_Android_JNI_FileClose(SDL_RWops* ctx, SDL_bool release)
1505 {
1506     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1507
1508     int result = 0;
1509     JNIEnv *mEnv = Android_JNI_GetEnv();
1510
1511     if (!LocalReferenceHolder_Init(&refs, mEnv)) {
1512         LocalReferenceHolder_Cleanup(&refs);
1513         return SDL_SetError("Failed to allocate enough JVM local references");
1514     }
1515
1516     if (ctx) {
1517         if (release) {
1518             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.fileNameRef);
1519         }
1520
1521         if (ctx->hidden.androidio.assetFileDescriptorRef) {
1522             jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
1523             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1524                     "close", "()V");
1525             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1526             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
1527             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1528                 result = -1;
1529             }
1530         }
1531         else {
1532             jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
1533
1534             /* inputStream.close(); */
1535             jmethodID mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
1536                     "close", "()V");
1537             (*mEnv)->CallVoidMethod(mEnv, inputStream, mid);
1538             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.inputStreamRef);
1539             (*mEnv)->DeleteGlobalRef(mEnv, (jobject)ctx->hidden.androidio.readableByteChannelRef);
1540             if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
1541                 result = -1;
1542             }
1543         }
1544
1545         if (release) {
1546             SDL_FreeRW(ctx);
1547         }
1548     }
1549
1550     LocalReferenceHolder_Cleanup(&refs);
1551     return result;
1552 }
1553
1554
1555 Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
1556 {
1557     return ctx->hidden.androidio.size;
1558 }
1559
1560 Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
1561 {
1562     if (ctx->hidden.androidio.assetFileDescriptorRef) {
1563         off_t ret;
1564         switch (whence) {
1565             case RW_SEEK_SET:
1566                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1567                 offset += ctx->hidden.androidio.offset;
1568                 break;
1569             case RW_SEEK_CUR:
1570                 offset += ctx->hidden.androidio.position;
1571                 if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
1572                 offset += ctx->hidden.androidio.offset;
1573                 break;
1574             case RW_SEEK_END:
1575                 offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
1576                 break;
1577             default:
1578                 return SDL_SetError("Unknown value for 'whence'");
1579         }
1580         whence = SEEK_SET;
1581
1582         ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
1583         if (ret == -1) return -1;
1584         ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
1585     } else {
1586         Sint64 newPosition;
1587         Sint64 movement;
1588
1589         switch (whence) {
1590             case RW_SEEK_SET:
1591                 newPosition = offset;
1592                 break;
1593             case RW_SEEK_CUR:
1594                 newPosition = ctx->hidden.androidio.position + offset;
1595                 break;
1596             case RW_SEEK_END:
1597                 newPosition = ctx->hidden.androidio.size + offset;
1598                 break;
1599             default:
1600                 return SDL_SetError("Unknown value for 'whence'");
1601         }
1602
1603         /* Validate the new position */
1604         if (newPosition < 0) {
1605             return SDL_Error(SDL_EFSEEK);
1606         }
1607         if (newPosition > ctx->hidden.androidio.size) {
1608             newPosition = ctx->hidden.androidio.size;
1609         }
1610
1611         movement = newPosition - ctx->hidden.androidio.position;
1612         if (movement > 0) {
1613             unsigned char buffer[4096];
1614
1615             /* The easy case where we're seeking forwards */
1616             while (movement > 0) {
1617                 Sint64 amount = sizeof (buffer);
1618                 size_t result;
1619                 if (amount > movement) {
1620                     amount = movement;
1621                 }
1622                 result = Android_JNI_FileRead(ctx, buffer, 1, amount);
1623                 if (result <= 0) {
1624                     /* Failed to read/skip the required amount, so fail */
1625                     return -1;
1626                 }
1627
1628                 movement -= result;
1629             }
1630
1631         } else if (movement < 0) {
1632             /* We can't seek backwards so we have to reopen the file and seek */
1633             /* forwards which obviously isn't very efficient */
1634             Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
1635             Internal_Android_JNI_FileOpen(ctx);
1636             Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1637         }
1638     }
1639
1640     return ctx->hidden.androidio.position;
1641
1642 }
1643
1644 int Android_JNI_FileClose(SDL_RWops* ctx)
1645 {
1646     return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
1647 }
1648
1649 int Android_JNI_SetClipboardText(const char* text)
1650 {
1651     JNIEnv* env = Android_JNI_GetEnv();
1652     jstring string = (*env)->NewStringUTF(env, text);
1653     (*env)->CallStaticVoidMethod(env, mActivityClass, midClipboardSetText, string);
1654     (*env)->DeleteLocalRef(env, string);
1655     return 0;
1656 }
1657
1658 char* Android_JNI_GetClipboardText(void)
1659 {
1660     JNIEnv* env = Android_JNI_GetEnv();
1661     char* text = NULL;
1662     jstring string;
1663     
1664     string = (*env)->CallStaticObjectMethod(env, mActivityClass, midClipboardGetText);
1665     if (string) {
1666         const char* utf = (*env)->GetStringUTFChars(env, string, 0);
1667         if (utf) {
1668             text = SDL_strdup(utf);
1669             (*env)->ReleaseStringUTFChars(env, string, utf);
1670         }
1671         (*env)->DeleteLocalRef(env, string);
1672     }
1673     
1674     return (text == NULL) ? SDL_strdup("") : text;
1675 }
1676
1677 SDL_bool Android_JNI_HasClipboardText(void)
1678 {
1679     JNIEnv* env = Android_JNI_GetEnv();
1680     jboolean retval = (*env)->CallStaticBooleanMethod(env, mActivityClass, midClipboardHasText);
1681     return (retval == JNI_TRUE) ? SDL_TRUE : SDL_FALSE;
1682 }
1683
1684 /* returns 0 on success or -1 on error (others undefined then)
1685  * returns truthy or falsy value in plugged, charged and battery
1686  * returns the value in seconds and percent or -1 if not available
1687  */
1688 int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1689 {
1690     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1691     JNIEnv* env = Android_JNI_GetEnv();
1692     jmethodID mid;
1693     jobject context;
1694     jstring action;
1695     jclass cls;
1696     jobject filter;
1697     jobject intent;
1698     jstring iname;
1699     jmethodID imid;
1700     jstring bname;
1701     jmethodID bmid;
1702     if (!LocalReferenceHolder_Init(&refs, env)) {
1703         LocalReferenceHolder_Cleanup(&refs);
1704         return -1;
1705     }
1706
1707
1708     /* context = SDLActivity.getContext(); */
1709     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1710
1711     action = (*env)->NewStringUTF(env, "android.intent.action.BATTERY_CHANGED");
1712
1713     cls = (*env)->FindClass(env, "android/content/IntentFilter");
1714
1715     mid = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;)V");
1716     filter = (*env)->NewObject(env, cls, mid, action);
1717
1718     (*env)->DeleteLocalRef(env, action);
1719
1720     mid = (*env)->GetMethodID(env, mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1721     intent = (*env)->CallObjectMethod(env, context, mid, NULL, filter);
1722
1723     (*env)->DeleteLocalRef(env, filter);
1724
1725     cls = (*env)->GetObjectClass(env, intent);
1726
1727     imid = (*env)->GetMethodID(env, cls, "getIntExtra", "(Ljava/lang/String;I)I");
1728
1729     /* Watch out for C89 scoping rules because of the macro */
1730 #define GET_INT_EXTRA(var, key) \
1731     int var; \
1732     iname = (*env)->NewStringUTF(env, key); \
1733     var = (*env)->CallIntMethod(env, intent, imid, iname, -1); \
1734     (*env)->DeleteLocalRef(env, iname);
1735
1736     bmid = (*env)->GetMethodID(env, cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1737
1738     /* Watch out for C89 scoping rules because of the macro */
1739 #define GET_BOOL_EXTRA(var, key) \
1740     int var; \
1741     bname = (*env)->NewStringUTF(env, key); \
1742     var = (*env)->CallBooleanMethod(env, intent, bmid, bname, JNI_FALSE); \
1743     (*env)->DeleteLocalRef(env, bname);
1744
1745     if (plugged) {
1746         /* Watch out for C89 scoping rules because of the macro */
1747         GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1748         if (plug == -1) {
1749             LocalReferenceHolder_Cleanup(&refs);
1750             return -1;
1751         }
1752         /* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1753         /* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1754         *plugged = (0 < plug) ? 1 : 0;
1755     }
1756
1757     if (charged) {
1758         /* Watch out for C89 scoping rules because of the macro */
1759         GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1760         if (status == -1) {
1761             LocalReferenceHolder_Cleanup(&refs);
1762             return -1;
1763         }
1764         /* 5 == BatteryManager.BATTERY_STATUS_FULL */
1765         *charged = (status == 5) ? 1 : 0;
1766     }
1767
1768     if (battery) {
1769         GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1770         *battery = present ? 1 : 0;
1771     }
1772
1773     if (seconds) {
1774         *seconds = -1; /* not possible */
1775     }
1776
1777     if (percent) {
1778         int level;
1779         int scale;
1780
1781         /* Watch out for C89 scoping rules because of the macro */
1782         {
1783             GET_INT_EXTRA(level_temp, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1784             level = level_temp;
1785         }
1786         /* Watch out for C89 scoping rules because of the macro */
1787         {
1788             GET_INT_EXTRA(scale_temp, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1789             scale = scale_temp;
1790         }
1791
1792         if ((level == -1) || (scale == -1)) {
1793             LocalReferenceHolder_Cleanup(&refs);
1794             return -1;
1795         }
1796         *percent = level * 100 / scale;
1797     }
1798
1799     (*env)->DeleteLocalRef(env, intent);
1800
1801     LocalReferenceHolder_Cleanup(&refs);
1802     return 0;
1803 }
1804
1805 /* returns number of found touch devices as return value and ids in parameter ids */
1806 int Android_JNI_GetTouchDeviceIds(int **ids) {
1807     JNIEnv *env = Android_JNI_GetEnv();
1808     jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1809     jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, midInputGetInputDeviceIds, sources);
1810     int number = 0;
1811     *ids = NULL;
1812     if (array) {
1813         number = (int) (*env)->GetArrayLength(env, array);
1814         if (0 < number) {
1815             jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1816             if (elements) {
1817                 int i;
1818                 *ids = SDL_malloc(number * sizeof (**ids));
1819                 for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1820                     (*ids)[i] = elements[i];
1821                 }
1822                 (*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1823             }
1824         }
1825         (*env)->DeleteLocalRef(env, array);
1826     }
1827     return number;
1828 }
1829
1830 /* sets the mSeparateMouseAndTouch field */
1831 void Android_JNI_SetSeparateMouseAndTouch(SDL_bool new_value)
1832 {
1833     JNIEnv *env = Android_JNI_GetEnv();
1834     (*env)->SetStaticBooleanField(env, mActivityClass, fidSeparateMouseAndTouch, new_value ? JNI_TRUE : JNI_FALSE);
1835 }
1836
1837 void Android_JNI_PollInputDevices(void)
1838 {
1839     JNIEnv *env = Android_JNI_GetEnv();
1840     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollInputDevices);
1841 }
1842
1843 void Android_JNI_PollHapticDevices(void)
1844 {
1845     JNIEnv *env = Android_JNI_GetEnv();
1846     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midPollHapticDevices);
1847 }
1848
1849 void Android_JNI_HapticRun(int device_id, int length)
1850 {
1851     JNIEnv *env = Android_JNI_GetEnv();
1852     (*env)->CallStaticVoidMethod(env, mControllerManagerClass, midHapticRun, device_id, length);
1853 }
1854
1855
1856 /* See SDLActivity.java for constants. */
1857 #define COMMAND_SET_KEEP_SCREEN_ON    5
1858
1859 /* sends message to be handled on the UI event dispatch thread */
1860 int Android_JNI_SendMessage(int command, int param)
1861 {
1862     JNIEnv *env = Android_JNI_GetEnv();
1863     jboolean success;
1864     success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midSendMessage, command, param);
1865     return success ? 0 : -1;
1866 }
1867
1868 void Android_JNI_SuspendScreenSaver(SDL_bool suspend)
1869 {
1870     Android_JNI_SendMessage(COMMAND_SET_KEEP_SCREEN_ON, (suspend == SDL_FALSE) ? 0 : 1);
1871 }
1872
1873 void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1874 {
1875     JNIEnv *env = Android_JNI_GetEnv();
1876     (*env)->CallStaticBooleanMethod(env, mActivityClass, midShowTextInput,
1877                                inputRect->x,
1878                                inputRect->y,
1879                                inputRect->w,
1880                                inputRect->h );
1881 }
1882
1883 void Android_JNI_HideTextInput(void)
1884 {
1885     /* has to match Activity constant */
1886     const int COMMAND_TEXTEDIT_HIDE = 3;
1887     Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1888 }
1889
1890 SDL_bool Android_JNI_IsScreenKeyboardShown()
1891 {
1892     JNIEnv *mEnv = Android_JNI_GetEnv();
1893     jboolean is_shown = 0;
1894     is_shown = (*mEnv)->CallStaticBooleanMethod(mEnv, mActivityClass, midIsScreenKeyboardShown);
1895     return is_shown;
1896 }
1897
1898
1899 int Android_JNI_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonid)
1900 {
1901     JNIEnv *env;
1902     jclass clazz;
1903     jmethodID mid;
1904     jobject context;
1905     jstring title;
1906     jstring message;
1907     jintArray button_flags;
1908     jintArray button_ids;
1909     jobjectArray button_texts;
1910     jintArray colors;
1911     jobject text;
1912     jint temp;
1913     int i;
1914
1915     env = Android_JNI_GetEnv();
1916
1917     /* convert parameters */
1918
1919     clazz = (*env)->FindClass(env, "java/lang/String");
1920
1921     title = (*env)->NewStringUTF(env, messageboxdata->title);
1922     message = (*env)->NewStringUTF(env, messageboxdata->message);
1923
1924     button_flags = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1925     button_ids = (*env)->NewIntArray(env, messageboxdata->numbuttons);
1926     button_texts = (*env)->NewObjectArray(env, messageboxdata->numbuttons,
1927         clazz, NULL);
1928     for (i = 0; i < messageboxdata->numbuttons; ++i) {
1929         temp = messageboxdata->buttons[i].flags;
1930         (*env)->SetIntArrayRegion(env, button_flags, i, 1, &temp);
1931         temp = messageboxdata->buttons[i].buttonid;
1932         (*env)->SetIntArrayRegion(env, button_ids, i, 1, &temp);
1933         text = (*env)->NewStringUTF(env, messageboxdata->buttons[i].text);
1934         (*env)->SetObjectArrayElement(env, button_texts, i, text);
1935         (*env)->DeleteLocalRef(env, text);
1936     }
1937
1938     if (messageboxdata->colorScheme) {
1939         colors = (*env)->NewIntArray(env, SDL_MESSAGEBOX_COLOR_MAX);
1940         for (i = 0; i < SDL_MESSAGEBOX_COLOR_MAX; ++i) {
1941             temp = (0xFF << 24) |
1942                    (messageboxdata->colorScheme->colors[i].r << 16) |
1943                    (messageboxdata->colorScheme->colors[i].g << 8) |
1944                    (messageboxdata->colorScheme->colors[i].b << 0);
1945             (*env)->SetIntArrayRegion(env, colors, i, 1, &temp);
1946         }
1947     } else {
1948         colors = NULL;
1949     }
1950
1951     (*env)->DeleteLocalRef(env, clazz);
1952
1953     /* context = SDLActivity.getContext(); */
1954     context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
1955
1956     clazz = (*env)->GetObjectClass(env, context);
1957
1958     mid = (*env)->GetMethodID(env, clazz,
1959         "messageboxShowMessageBox", "(ILjava/lang/String;Ljava/lang/String;[I[I[Ljava/lang/String;[I)I");
1960     *buttonid = (*env)->CallIntMethod(env, context, mid,
1961         messageboxdata->flags,
1962         title,
1963         message,
1964         button_flags,
1965         button_ids,
1966         button_texts,
1967         colors);
1968
1969     (*env)->DeleteLocalRef(env, context);
1970     (*env)->DeleteLocalRef(env, clazz);
1971
1972     /* delete parameters */
1973
1974     (*env)->DeleteLocalRef(env, title);
1975     (*env)->DeleteLocalRef(env, message);
1976     (*env)->DeleteLocalRef(env, button_flags);
1977     (*env)->DeleteLocalRef(env, button_ids);
1978     (*env)->DeleteLocalRef(env, button_texts);
1979     (*env)->DeleteLocalRef(env, colors);
1980
1981     return 0;
1982 }
1983
1984 /*
1985 //////////////////////////////////////////////////////////////////////////////
1986 //
1987 // Functions exposed to SDL applications in SDL_system.h
1988 //////////////////////////////////////////////////////////////////////////////
1989 */
1990
1991 void *SDL_AndroidGetJNIEnv(void)
1992 {
1993     return Android_JNI_GetEnv();
1994 }
1995
1996 void *SDL_AndroidGetActivity(void)
1997 {
1998     /* See SDL_system.h for caveats on using this function. */
1999
2000     JNIEnv *env = Android_JNI_GetEnv();
2001     if (!env) {
2002         return NULL;
2003     }
2004
2005     /* return SDLActivity.getContext(); */
2006     return (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2007 }
2008
2009 SDL_bool SDL_IsAndroidTV(void)
2010 {
2011     JNIEnv *env = Android_JNI_GetEnv();
2012     return (*env)->CallStaticBooleanMethod(env, mActivityClass, midIsAndroidTV);
2013 }
2014
2015 const char * SDL_AndroidGetInternalStoragePath(void)
2016 {
2017     static char *s_AndroidInternalFilesPath = NULL;
2018
2019     if (!s_AndroidInternalFilesPath) {
2020         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2021         jmethodID mid;
2022         jobject context;
2023         jobject fileObject;
2024         jstring pathString;
2025         const char *path;
2026
2027         JNIEnv *env = Android_JNI_GetEnv();
2028         if (!LocalReferenceHolder_Init(&refs, env)) {
2029             LocalReferenceHolder_Cleanup(&refs);
2030             return NULL;
2031         }
2032
2033         /* context = SDLActivity.getContext(); */
2034         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2035         if (!context) {
2036             SDL_SetError("Couldn't get Android context!");
2037             LocalReferenceHolder_Cleanup(&refs);
2038             return NULL;
2039         }
2040
2041         /* fileObj = context.getFilesDir(); */
2042         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2043                 "getFilesDir", "()Ljava/io/File;");
2044         fileObject = (*env)->CallObjectMethod(env, context, mid);
2045         if (!fileObject) {
2046             SDL_SetError("Couldn't get internal directory");
2047             LocalReferenceHolder_Cleanup(&refs);
2048             return NULL;
2049         }
2050
2051         /* path = fileObject.getCanonicalPath(); */
2052         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2053                 "getCanonicalPath", "()Ljava/lang/String;");
2054         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2055         if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
2056             LocalReferenceHolder_Cleanup(&refs);
2057             return NULL;
2058         }
2059
2060         path = (*env)->GetStringUTFChars(env, pathString, NULL);
2061         s_AndroidInternalFilesPath = SDL_strdup(path);
2062         (*env)->ReleaseStringUTFChars(env, pathString, path);
2063
2064         LocalReferenceHolder_Cleanup(&refs);
2065     }
2066     return s_AndroidInternalFilesPath;
2067 }
2068
2069 int SDL_AndroidGetExternalStorageState(void)
2070 {
2071     struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2072     jmethodID mid;
2073     jclass cls;
2074     jstring stateString;
2075     const char *state;
2076     int stateFlags;
2077
2078     JNIEnv *env = Android_JNI_GetEnv();
2079     if (!LocalReferenceHolder_Init(&refs, env)) {
2080         LocalReferenceHolder_Cleanup(&refs);
2081         return 0;
2082     }
2083
2084     cls = (*env)->FindClass(env, "android/os/Environment");
2085     mid = (*env)->GetStaticMethodID(env, cls,
2086             "getExternalStorageState", "()Ljava/lang/String;");
2087     stateString = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
2088
2089     state = (*env)->GetStringUTFChars(env, stateString, NULL);
2090
2091     /* Print an info message so people debugging know the storage state */
2092     __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
2093
2094     if (SDL_strcmp(state, "mounted") == 0) {
2095         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
2096                      SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
2097     } else if (SDL_strcmp(state, "mounted_ro") == 0) {
2098         stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
2099     } else {
2100         stateFlags = 0;
2101     }
2102     (*env)->ReleaseStringUTFChars(env, stateString, state);
2103
2104     LocalReferenceHolder_Cleanup(&refs);
2105     return stateFlags;
2106 }
2107
2108 const char * SDL_AndroidGetExternalStoragePath(void)
2109 {
2110     static char *s_AndroidExternalFilesPath = NULL;
2111
2112     if (!s_AndroidExternalFilesPath) {
2113         struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
2114         jmethodID mid;
2115         jobject context;
2116         jobject fileObject;
2117         jstring pathString;
2118         const char *path;
2119
2120         JNIEnv *env = Android_JNI_GetEnv();
2121         if (!LocalReferenceHolder_Init(&refs, env)) {
2122             LocalReferenceHolder_Cleanup(&refs);
2123             return NULL;
2124         }
2125
2126         /* context = SDLActivity.getContext(); */
2127         context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
2128
2129         /* fileObj = context.getExternalFilesDir(); */
2130         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
2131                 "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
2132         fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
2133         if (!fileObject) {
2134             SDL_SetError("Couldn't get external directory");
2135             LocalReferenceHolder_Cleanup(&refs);
2136             return NULL;
2137         }
2138
2139         /* path = fileObject.getAbsolutePath(); */
2140         mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
2141                 "getAbsolutePath", "()Ljava/lang/String;");
2142         pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
2143
2144         path = (*env)->GetStringUTFChars(env, pathString, NULL);
2145         s_AndroidExternalFilesPath = SDL_strdup(path);
2146         (*env)->ReleaseStringUTFChars(env, pathString, path);
2147
2148         LocalReferenceHolder_Cleanup(&refs);
2149     }
2150     return s_AndroidExternalFilesPath;
2151 }
2152
2153 void Android_JNI_GetManifestEnvironmentVariables(void)
2154 {
2155     if (!mActivityClass || !midGetManifestEnvironmentVariables) {
2156         __android_log_print(ANDROID_LOG_WARN, "SDL", "Request to get environment variables before JNI is ready");
2157         return;
2158     }
2159
2160     if (!bHasEnvironmentVariables) {
2161         JNIEnv *env = Android_JNI_GetEnv();
2162         SDL_bool ret = (*env)->CallStaticBooleanMethod(env, mActivityClass, midGetManifestEnvironmentVariables);
2163         if (ret) {
2164             bHasEnvironmentVariables = SDL_TRUE;
2165         }
2166     }
2167 }
2168
2169 #endif /* __ANDROID__ */
2170
2171 /* vi: set ts=4 sw=4 expandtab: */