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