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