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