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