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