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