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