zenilib  0.5.3.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
SDL_android.cpp
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2013 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_config.h"
22 #include "SDL_stdinc.h"
23 #include "SDL_assert.h"
24 #include "SDL_log.h"
25 
26 #ifdef __ANDROID__
27 
28 #include "SDL_system.h"
29 #include "SDL_android.h"
30 #include <EGL/egl.h>
31 
32 extern "C" {
33 #include "../../events/SDL_events_c.h"
34 #include "../../video/android/SDL_androidkeyboard.h"
35 #include "../../video/android/SDL_androidtouch.h"
36 #include "../../video/android/SDL_androidvideo.h"
37 
38 #include <android/log.h>
39 #include <pthread.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42 #define LOG_TAG "SDL_android"
43 //#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
44 //#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
45 #define LOGI(...) do {} while (false)
46 #define LOGE(...) do {} while (false)
47 
48 /* Uncomment this to log messages entering and exiting methods in this file */
49 //#define DEBUG_JNI
50 
51 /* Implemented in audio/android/SDL_androidaudio.c */
52 extern void Android_RunAudioThread();
53 
54 static void Android_JNI_ThreadDestroyed(void*);
55 } // C
56 
57 /*******************************************************************************
58  This file links the Java side of Android with libsdl
59 *******************************************************************************/
60 #include <jni.h>
61 #include <android/log.h>
62 
63 
64 /*******************************************************************************
65  Globals
66 *******************************************************************************/
67 static pthread_key_t mThreadKey;
68 static JavaVM* mJavaVM;
69 
70 // Main activity
71 static jclass mActivityClass;
72 
73 // method signatures
74 static jmethodID midCreateGLContext;
75 static jmethodID midFlipBuffers;
76 static jmethodID midAudioInit;
77 static jmethodID midAudioWriteShortBuffer;
78 static jmethodID midAudioWriteByteBuffer;
79 static jmethodID midAudioQuit;
80 
81 // Accelerometer data storage
82 static float fLastAccelerometer[3];
83 static bool bHasNewData;
84 
85 /*******************************************************************************
86  Functions called by JNI
87 *******************************************************************************/
88 
89 // Library init
90 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
91 {
92  JNIEnv *env;
93  mJavaVM = vm;
94  LOGI("JNI_OnLoad called");
95  if (mJavaVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
96  LOGE("Failed to get the environment using GetEnv()");
97  return -1;
98  }
99  /*
100  * Create mThreadKey so we can keep track of the JNIEnv assigned to each thread
101  * Refer to http://developer.android.com/guide/practices/design/jni.html for the rationale behind this
102  */
103  if (pthread_key_create(&mThreadKey, Android_JNI_ThreadDestroyed)) {
104  __android_log_print(ANDROID_LOG_ERROR, "SDL", "Error initializing pthread key");
105  }
106  else {
108  }
109 
110  return JNI_VERSION_1_4;
111 }
112 
113 // Called before SDL_main() to initialize JNI bindings
114 extern "C" void SDL_Android_Init(JNIEnv* mEnv, jclass cls)
115 {
116  __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
117 
119 
120  mActivityClass = (jclass)mEnv->NewGlobalRef(cls);
121 
122  midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
123  "createGLContext","(II[I)Z");
124  midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
125  "flipBuffers","()V");
126  midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
127  "audioInit", "(IZZI)V");
128  midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
129  "audioWriteShortBuffer", "([S)V");
130  midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
131  "audioWriteByteBuffer", "([B)V");
132  midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
133  "audioQuit", "()V");
134 
135  bHasNewData = false;
136 
137  if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
138  !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
139  __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
140  }
141  __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
142 }
143 
144 // Resize
145 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
146  JNIEnv* env, jclass jcls,
147  jint width, jint height, jint format)
148 {
149  Android_SetScreenResolution(width, height, format);
150 }
151 
152 // Keydown
153 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
154  JNIEnv* env, jclass jcls, jint keycode)
155 {
156  Android_OnKeyDown(keycode);
157 }
158 
159 // Keyup
160 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
161  JNIEnv* env, jclass jcls, jint keycode)
162 {
163  Android_OnKeyUp(keycode);
164 }
165 
166 // Touch
167 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
168  JNIEnv* env, jclass jcls,
169  jint touch_device_id_in, jint pointer_finger_id_in,
170  jint action, jfloat x, jfloat y, jfloat p)
171 {
172  Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
173 }
174 
175 // Accelerometer
176 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
177  JNIEnv* env, jclass jcls,
178  jfloat x, jfloat y, jfloat z)
179 {
180  fLastAccelerometer[0] = x;
181  fLastAccelerometer[1] = y;
182  fLastAccelerometer[2] = z;
183  bHasNewData = true;
184 }
185 
186 // Low memory
187 extern "C" void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
188  JNIEnv* env, jclass cls)
189 {
191 }
192 
193 // Quit
194 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
195  JNIEnv* env, jclass cls)
196 {
197  // Inject a SDL_QUIT event
198  SDL_SendQuit();
200 }
201 
202 // Pause
203 extern "C" void Java_org_libsdl_app_SDLActivity_nativePause(
204  JNIEnv* env, jclass cls)
205 {
206  if (Android_Window) {
207  /* Signal the pause semaphore so the event loop knows to pause and (optionally) block itself */
211  }
212 
213  __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativePause()");
216 }
217 
218 // Resume
219 extern "C" void Java_org_libsdl_app_SDLActivity_nativeResume(
220  JNIEnv* env, jclass cls)
221 {
222  __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "nativeResume()");
225 
226  if (Android_Window) {
227  /* Signal the resume semaphore so the event loop knows to resume and restore the GL Context
228  * We can't restore the GL Context here because it needs to be done on the SDL main thread
229  * and this function will be called from the Java thread instead.
230  */
234  }
235 }
236 
237 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
238  JNIEnv* env, jclass cls)
239 {
240  /* This is the audio thread, with a different environment */
242 
243  Android_RunAudioThread();
244 }
245 
246 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeCommitText(
247  JNIEnv* env, jclass cls,
248  jstring text, jint newCursorPosition)
249 {
250  const char *utftext = env->GetStringUTFChars(text, NULL);
251 
252  SDL_SendKeyboardText(utftext);
253 
254  env->ReleaseStringUTFChars(text, utftext);
255 }
256 
257 extern "C" void Java_org_libsdl_app_SDLInputConnection_nativeSetComposingText(
258  JNIEnv* env, jclass cls,
259  jstring text, jint newCursorPosition)
260 {
261  const char *utftext = env->GetStringUTFChars(text, NULL);
262 
263  SDL_SendEditingText(utftext, 0, 0);
264 
265  env->ReleaseStringUTFChars(text, utftext);
266 }
267 
268 
269 
270 /*******************************************************************************
271  Functions called by SDL into Java
272 *******************************************************************************/
273 
274 class LocalReferenceHolder
275 {
276 private:
277  static int s_active;
278 
279 public:
280  static bool IsActive() {
281  return s_active > 0;
282  }
283 
284 public:
285  LocalReferenceHolder(const char *func) : m_env(NULL), m_func(func) {
286 #ifdef DEBUG_JNI
287  SDL_Log("Entering function %s", m_func);
288 #endif
289  }
290  ~LocalReferenceHolder() {
291 #ifdef DEBUG_JNI
292  SDL_Log("Leaving function %s", m_func);
293 #endif
294  if (m_env) {
295  m_env->PopLocalFrame(NULL);
296  --s_active;
297  }
298  }
299 
300  bool init(JNIEnv *env, jint capacity = 16) {
301  if (env->PushLocalFrame(capacity) < 0) {
302  SDL_SetError("Failed to allocate enough JVM local references");
303  return false;
304  }
305  ++s_active;
306  m_env = env;
307  return true;
308  }
309 
310 protected:
311  JNIEnv *m_env;
312  const char *m_func;
313 };
314 int LocalReferenceHolder::s_active;
315 
316 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
317  int red, int green, int blue, int alpha,
318  int buffer, int depth, int stencil,
319  int buffers, int samples)
320 {
321  JNIEnv *env = Android_JNI_GetEnv();
322 
323  jint attribs[] = {
324  EGL_RED_SIZE, red,
334  EGL_NONE
335  };
336  int len = SDL_arraysize(attribs);
337 
338  jintArray array;
339 
340  array = env->NewIntArray(len);
341  env->SetIntArrayRegion(array, 0, len, attribs);
342 
343  jboolean success = env->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
344 
345  env->DeleteLocalRef(array);
346 
347  return success ? SDL_TRUE : SDL_FALSE;
348 }
349 
350 extern "C" void Android_JNI_SwapWindow()
351 {
352  JNIEnv *mEnv = Android_JNI_GetEnv();
353  mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
354 }
355 
356 extern "C" void Android_JNI_SetActivityTitle(const char *title)
357 {
358  jmethodID mid;
359  JNIEnv *mEnv = Android_JNI_GetEnv();
360  mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)Z");
361  if (mid) {
362  jstring jtitle = reinterpret_cast<jstring>(mEnv->NewStringUTF(title));
363  mEnv->CallStaticBooleanMethod(mActivityClass, mid, jtitle);
364  mEnv->DeleteLocalRef(jtitle);
365  }
366 }
367 
369 {
370  int i;
371  SDL_bool retval = SDL_FALSE;
372 
373  if (bHasNewData) {
374  for (i = 0; i < 3; ++i) {
375  values[i] = fLastAccelerometer[i];
376  }
377  bHasNewData = false;
378  retval = SDL_TRUE;
379  }
380 
381  return retval;
382 }
383 
384 static void Android_JNI_ThreadDestroyed(void* value) {
385  /* The thread is being destroyed, detach it from the Java VM and set the mThreadKey value to NULL as required */
386  JNIEnv *env = (JNIEnv*) value;
387  if (env != NULL) {
388  mJavaVM->DetachCurrentThread();
389  pthread_setspecific(mThreadKey, NULL);
390  }
391 }
392 
393 JNIEnv* Android_JNI_GetEnv(void) {
394  /* From http://developer.android.com/guide/practices/jni.html
395  * All threads are Linux threads, scheduled by the kernel.
396  * They're usually started from managed code (using Thread.start), but they can also be created elsewhere and then
397  * attached to the JavaVM. For example, a thread started with pthread_create can be attached with the
398  * JNI AttachCurrentThread or AttachCurrentThreadAsDaemon functions. Until a thread is attached, it has no JNIEnv,
399  * and cannot make JNI calls.
400  * Attaching a natively-created thread causes a java.lang.Thread object to be constructed and added to the "main"
401  * ThreadGroup, making it visible to the debugger. Calling AttachCurrentThread on an already-attached thread
402  * is a no-op.
403  * Note: You can call this function any number of times for the same thread, there's no harm in it
404  */
405 
406  JNIEnv *env;
407  int status = mJavaVM->AttachCurrentThread(&env, NULL);
408  if(status < 0) {
409  LOGE("failed to attach current thread");
410  return 0;
411  }
412 
413  return env;
414 }
415 
416 int Android_JNI_SetupThread(void) {
417  /* From http://developer.android.com/guide/practices/jni.html
418  * Threads attached through JNI must call DetachCurrentThread before they exit. If coding this directly is awkward,
419  * in Android 2.0 (Eclair) and higher you can use pthread_key_create to define a destructor function that will be
420  * called before the thread exits, and call DetachCurrentThread from there. (Use that key with pthread_setspecific
421  * to store the JNIEnv in thread-local-storage; that way it'll be passed into your destructor as the argument.)
422  * Note: The destructor is not called unless the stored value is != NULL
423  * Note: You can call this function any number of times for the same thread, there's no harm in it
424  * (except for some lost CPU cycles)
425  */
426  JNIEnv *env = Android_JNI_GetEnv();
427  pthread_setspecific(mThreadKey, (void*) env);
428  return 1;
429 }
430 
431 //
432 // Audio support
433 //
434 static jboolean audioBuffer16Bit = JNI_FALSE;
435 static jboolean audioBufferStereo = JNI_FALSE;
436 static jobject audioBuffer = NULL;
437 static void* audioBufferPinned = NULL;
438 
439 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
440 {
441  int audioBufferFrames;
442 
443  JNIEnv *env = Android_JNI_GetEnv();
444 
445  if (!env) {
446  LOGE("callback_handler: failed to attach current thread");
447  }
449 
450  __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
451  audioBuffer16Bit = is16Bit;
452  audioBufferStereo = channelCount > 1;
453 
454  env->CallStaticVoidMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
455 
456  /* Allocating the audio buffer from the Java side and passing it as the return value for audioInit no longer works on
457  * Android >= 4.2 due to a "stale global reference" error. So now we allocate this buffer directly from this side. */
458 
459  if (is16Bit) {
460  jshortArray audioBufferLocal = env->NewShortArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
461  if (audioBufferLocal) {
462  audioBuffer = env->NewGlobalRef(audioBufferLocal);
463  env->DeleteLocalRef(audioBufferLocal);
464  }
465  }
466  else {
467  jbyteArray audioBufferLocal = env->NewByteArray(desiredBufferFrames * (audioBufferStereo ? 2 : 1));
468  if (audioBufferLocal) {
469  audioBuffer = env->NewGlobalRef(audioBufferLocal);
470  env->DeleteLocalRef(audioBufferLocal);
471  }
472  }
473 
474  if (audioBuffer == NULL) {
475  __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: could not allocate an audio buffer!");
476  return 0;
477  }
478 
479  jboolean isCopy = JNI_FALSE;
480  if (audioBuffer16Bit) {
481  audioBufferPinned = env->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
482  audioBufferFrames = env->GetArrayLength((jshortArray)audioBuffer);
483  } else {
484  audioBufferPinned = env->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
485  audioBufferFrames = env->GetArrayLength((jbyteArray)audioBuffer);
486  }
487  if (audioBufferStereo) {
488  audioBufferFrames /= 2;
489  }
490 
491  return audioBufferFrames;
492 }
493 
494 extern "C" void * Android_JNI_GetAudioBuffer()
495 {
496  return audioBufferPinned;
497 }
498 
499 extern "C" void Android_JNI_WriteAudioBuffer()
500 {
501  JNIEnv *mAudioEnv = Android_JNI_GetEnv();
502 
503  if (audioBuffer16Bit) {
504  mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
505  mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
506  } else {
507  mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
508  mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
509  }
510 
511  /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
512 }
513 
514 extern "C" void Android_JNI_CloseAudioDevice()
515 {
516  JNIEnv *env = Android_JNI_GetEnv();
517 
518  env->CallStaticVoidMethod(mActivityClass, midAudioQuit);
519 
520  if (audioBuffer) {
521  env->DeleteGlobalRef(audioBuffer);
522  audioBuffer = NULL;
523  audioBufferPinned = NULL;
524  }
525 }
526 
527 // Test for an exception and call SDL_SetError with its detail if one occurs
528 // If optional parameter silent is truthy then SDL_SetError() is not called.
529 static bool Android_JNI_ExceptionOccurred(bool silent = false)
530 {
531  SDL_assert(LocalReferenceHolder::IsActive());
532  JNIEnv *mEnv = Android_JNI_GetEnv();
533 
534  jthrowable exception = mEnv->ExceptionOccurred();
535  if (exception != NULL) {
536  jmethodID mid;
537 
538  // Until this happens most JNI operations have undefined behaviour
539  mEnv->ExceptionClear();
540 
541  if (!silent) {
542  jclass exceptionClass = mEnv->GetObjectClass(exception);
543  jclass classClass = mEnv->FindClass("java/lang/Class");
544 
545  mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
546  jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
547  const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
548 
549  mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
550  jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
551 
552  if (exceptionMessage != NULL) {
553  const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(exceptionMessage, 0);
554  SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
555  mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
556  } else {
557  SDL_SetError("%s", exceptionNameUTF8);
558  }
559 
560  mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
561  }
562 
563  return true;
564  }
565 
566  return false;
567 }
568 
570 {
571  LocalReferenceHolder refs(__FUNCTION__);
572  int result = 0;
573 
574  jmethodID mid;
575  jobject context;
576  jobject assetManager;
577  jobject inputStream;
578  jclass channels;
579  jobject readableByteChannel;
580  jstring fileNameJString;
581  jobject fd;
582  jclass fdCls;
583  jfieldID descriptor;
584 
585  JNIEnv *mEnv = Android_JNI_GetEnv();
586  if (!refs.init(mEnv)) {
587  goto failure;
588  }
589 
590  fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
591  ctx->hidden.androidio.position = 0;
592 
593  // context = SDLActivity.getContext();
594  mid = mEnv->GetStaticMethodID(mActivityClass,
595  "getContext","()Landroid/content/Context;");
596  context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
597 
598 
599  // assetManager = context.getAssets();
600  mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
601  "getAssets", "()Landroid/content/res/AssetManager;");
602  assetManager = mEnv->CallObjectMethod(context, mid);
603 
604  /* First let's try opening the file to obtain an AssetFileDescriptor.
605  * This method reads the files directly from the APKs using standard *nix calls
606  */
607  mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
608  inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
609  if (Android_JNI_ExceptionOccurred(true)) {
610  goto fallback;
611  }
612 
613  mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getStartOffset", "()J");
614  ctx->hidden.androidio.offset = mEnv->CallLongMethod(inputStream, mid);
615  if (Android_JNI_ExceptionOccurred(true)) {
616  goto fallback;
617  }
618 
619  mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getDeclaredLength", "()J");
620  ctx->hidden.androidio.size = mEnv->CallLongMethod(inputStream, mid);
621  if (Android_JNI_ExceptionOccurred(true)) {
622  goto fallback;
623  }
624 
625  mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
626  fd = mEnv->CallObjectMethod(inputStream, mid);
627  fdCls = mEnv->GetObjectClass(fd);
628  descriptor = mEnv->GetFieldID(fdCls, "descriptor", "I");
629  ctx->hidden.androidio.fd = mEnv->GetIntField(fd, descriptor);
630  ctx->hidden.androidio.assetFileDescriptorRef = mEnv->NewGlobalRef(inputStream);
631 
632  // Seek to the correct offset in the file.
633  lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
634 
635  if (false) {
636 fallback:
637  // Disabled log message because of spam on the Nexus 7
638  //__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
639 
640  /* Try the old method using InputStream */
641  ctx->hidden.androidio.assetFileDescriptorRef = NULL;
642 
643  // inputStream = assetManager.open(<filename>);
644  mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
645  "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
646  inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
647  if (Android_JNI_ExceptionOccurred()) {
648  goto failure;
649  }
650 
651  ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
652 
653  // Despite all the visible documentation on [Asset]InputStream claiming
654  // that the .available() method is not guaranteed to return the entire file
655  // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
656  // android/apis/content/ReadAsset.java imply that Android's
657  // AssetInputStream.available() /will/ always return the total file size
658 
659  // size = inputStream.available();
660  mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
661  "available", "()I");
662  ctx->hidden.androidio.size = (long)mEnv->CallIntMethod(inputStream, mid);
663  if (Android_JNI_ExceptionOccurred()) {
664  goto failure;
665  }
666 
667  // readableByteChannel = Channels.newChannel(inputStream);
668  channels = mEnv->FindClass("java/nio/channels/Channels");
669  mid = mEnv->GetStaticMethodID(channels,
670  "newChannel",
671  "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
672  readableByteChannel = mEnv->CallStaticObjectMethod(
673  channels, mid, inputStream);
674  if (Android_JNI_ExceptionOccurred()) {
675  goto failure;
676  }
677 
678  ctx->hidden.androidio.readableByteChannelRef =
679  mEnv->NewGlobalRef(readableByteChannel);
680 
681  // Store .read id for reading purposes
682  mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
683  "read", "(Ljava/nio/ByteBuffer;)I");
684  ctx->hidden.androidio.readMethod = mid;
685  }
686 
687  if (false) {
688 failure:
689  result = -1;
690 
691  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
692 
693  if(ctx->hidden.androidio.inputStreamRef != NULL) {
694  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
695  }
696 
697  if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
698  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
699  }
700 
701  if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
702  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
703  }
704 
705  }
706 
707  return result;
708 }
709 
710 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
711  const char* fileName, const char*)
712 {
713  LocalReferenceHolder refs(__FUNCTION__);
714  JNIEnv *mEnv = Android_JNI_GetEnv();
715 
716  if (!refs.init(mEnv)) {
717  return -1;
718  }
719 
720  if (!ctx) {
721  return -1;
722  }
723 
724  jstring fileNameJString = mEnv->NewStringUTF(fileName);
725  ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
726  ctx->hidden.androidio.inputStreamRef = NULL;
727  ctx->hidden.androidio.readableByteChannelRef = NULL;
728  ctx->hidden.androidio.readMethod = NULL;
729  ctx->hidden.androidio.assetFileDescriptorRef = NULL;
730 
731  return Android_JNI_FileOpen(ctx);
732 }
733 
734 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
735  size_t size, size_t maxnum)
736 {
737  LocalReferenceHolder refs(__FUNCTION__);
738 
739  if (ctx->hidden.androidio.assetFileDescriptorRef) {
740  size_t bytesMax = size * maxnum;
741  if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
742  bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
743  }
744  size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
745  if (result > 0) {
746  ctx->hidden.androidio.position += result;
747  return result / size;
748  }
749  return 0;
750  } else {
751  jlong bytesRemaining = (jlong) (size * maxnum);
752  jlong bytesMax = (jlong) (ctx->hidden.androidio.size - ctx->hidden.androidio.position);
753  int bytesRead = 0;
754 
755  /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
756  if (bytesRemaining > bytesMax) bytesRemaining = bytesMax;
757 
758  JNIEnv *mEnv = Android_JNI_GetEnv();
759  if (!refs.init(mEnv)) {
760  return -1;
761  }
762 
763  jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
764  jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
765  jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
766 
767  while (bytesRemaining > 0) {
768  // result = readableByteChannel.read(...);
769  int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
770 
771  if (Android_JNI_ExceptionOccurred()) {
772  return 0;
773  }
774 
775  if (result < 0) {
776  break;
777  }
778 
779  bytesRemaining -= result;
780  bytesRead += result;
781  ctx->hidden.androidio.position += result;
782  }
783  return bytesRead / size;
784  }
785 }
786 
787 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
788  size_t size, size_t num)
789 {
790  SDL_SetError("Cannot write to Android package filesystem");
791  return 0;
792 }
793 
794 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
795 {
796  LocalReferenceHolder refs(__FUNCTION__);
797  int result = 0;
798  JNIEnv *mEnv = Android_JNI_GetEnv();
799 
800  if (!refs.init(mEnv)) {
801  return SDL_SetError("Failed to allocate enough JVM local references");
802  }
803 
804  if (ctx) {
805  if (release) {
806  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
807  }
808 
809  if (ctx->hidden.androidio.assetFileDescriptorRef) {
810  jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
811  jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
812  "close", "()V");
813  mEnv->CallVoidMethod(inputStream, mid);
814  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.assetFileDescriptorRef);
815  if (Android_JNI_ExceptionOccurred()) {
816  result = -1;
817  }
818  }
819  else {
820  jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
821 
822  // inputStream.close();
823  jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
824  "close", "()V");
825  mEnv->CallVoidMethod(inputStream, mid);
826  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
827  mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
828  if (Android_JNI_ExceptionOccurred()) {
829  result = -1;
830  }
831  }
832 
833  if (release) {
834  SDL_FreeRW(ctx);
835  }
836  }
837 
838  return result;
839 }
840 
841 
842 extern "C" Sint64 Android_JNI_FileSize(SDL_RWops* ctx)
843 {
844  return ctx->hidden.androidio.size;
845 }
846 
847 extern "C" Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
848 {
849  if (ctx->hidden.androidio.assetFileDescriptorRef) {
850  switch (whence) {
851  case RW_SEEK_SET:
852  if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
853  offset += ctx->hidden.androidio.offset;
854  break;
855  case RW_SEEK_CUR:
856  offset += ctx->hidden.androidio.position;
857  if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
858  offset += ctx->hidden.androidio.offset;
859  break;
860  case RW_SEEK_END:
861  offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
862  break;
863  default:
864  return SDL_SetError("Unknown value for 'whence'");
865  }
866  whence = SEEK_SET;
867 
868  off_t ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
869  if (ret == -1) return -1;
870  ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
871  } else {
872  Sint64 newPosition;
873 
874  switch (whence) {
875  case RW_SEEK_SET:
876  newPosition = offset;
877  break;
878  case RW_SEEK_CUR:
879  newPosition = ctx->hidden.androidio.position + offset;
880  break;
881  case RW_SEEK_END:
882  newPosition = ctx->hidden.androidio.size + offset;
883  break;
884  default:
885  return SDL_SetError("Unknown value for 'whence'");
886  }
887 
888  /* Validate the new position */
889  if (newPosition < 0) {
890  return SDL_Error(SDL_EFSEEK);
891  }
892  if (newPosition > ctx->hidden.androidio.size) {
893  newPosition = ctx->hidden.androidio.size;
894  }
895 
896  Sint64 movement = newPosition - ctx->hidden.androidio.position;
897  if (movement > 0) {
898  unsigned char buffer[4096];
899 
900  // The easy case where we're seeking forwards
901  while (movement > 0) {
902  Sint64 amount = sizeof (buffer);
903  if (amount > movement) {
904  amount = movement;
905  }
906  size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
907  if (result <= 0) {
908  // Failed to read/skip the required amount, so fail
909  return -1;
910  }
911 
912  movement -= result;
913  }
914 
915  } else if (movement < 0) {
916  // We can't seek backwards so we have to reopen the file and seek
917  // forwards which obviously isn't very efficient
918  Android_JNI_FileClose(ctx, false);
920  Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
921  }
922  }
923 
924  return ctx->hidden.androidio.position;
925 
926 }
927 
928 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
929 {
930  return Android_JNI_FileClose(ctx, true);
931 }
932 
933 // returns a new global reference which needs to be released later
934 static jobject Android_JNI_GetSystemServiceObject(const char* name)
935 {
936  LocalReferenceHolder refs(__FUNCTION__);
937  JNIEnv* env = Android_JNI_GetEnv();
938  if (!refs.init(env)) {
939  return NULL;
940  }
941 
942  jstring service = env->NewStringUTF(name);
943 
944  jmethodID mid;
945 
946  mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
947  jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
948 
949  mid = env->GetMethodID(mActivityClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
950  jobject manager = env->CallObjectMethod(context, mid, service);
951 
952  env->DeleteLocalRef(service);
953 
954  return manager ? env->NewGlobalRef(manager) : NULL;
955 }
956 
957 #define SETUP_CLIPBOARD(error) \
958  LocalReferenceHolder refs(__FUNCTION__); \
959  JNIEnv* env = Android_JNI_GetEnv(); \
960  if (!refs.init(env)) { \
961  return error; \
962  } \
963  jobject clipboard = Android_JNI_GetSystemServiceObject("clipboard"); \
964  if (!clipboard) { \
965  return error; \
966  }
967 
968 extern "C" int Android_JNI_SetClipboardText(const char* text)
969 {
970  SETUP_CLIPBOARD(-1)
971 
972  jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "setText", "(Ljava/lang/CharSequence;)V");
973  jstring string = env->NewStringUTF(text);
974  env->CallVoidMethod(clipboard, mid, string);
975  env->DeleteGlobalRef(clipboard);
976  env->DeleteLocalRef(string);
977  return 0;
978 }
979 
980 extern "C" char* Android_JNI_GetClipboardText()
981 {
982  SETUP_CLIPBOARD(SDL_strdup(""))
983 
984  jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "getText", "()Ljava/lang/CharSequence;");
985  jobject sequence = env->CallObjectMethod(clipboard, mid);
986  env->DeleteGlobalRef(clipboard);
987  if (sequence) {
988  mid = env->GetMethodID(env->GetObjectClass(sequence), "toString", "()Ljava/lang/String;");
989  jstring string = reinterpret_cast<jstring>(env->CallObjectMethod(sequence, mid));
990  const char* utf = env->GetStringUTFChars(string, 0);
991  if (utf) {
992  char* text = SDL_strdup(utf);
993  env->ReleaseStringUTFChars(string, utf);
994  return text;
995  }
996  }
997  return SDL_strdup("");
998 }
999 
1001 {
1002  SETUP_CLIPBOARD(SDL_FALSE)
1003 
1004  jmethodID mid = env->GetMethodID(env->GetObjectClass(clipboard), "hasText", "()Z");
1005  jboolean has = env->CallBooleanMethod(clipboard, mid);
1006  env->DeleteGlobalRef(clipboard);
1007  return has ? SDL_TRUE : SDL_FALSE;
1008 }
1009 
1010 
1011 // returns 0 on success or -1 on error (others undefined then)
1012 // returns truthy or falsy value in plugged, charged and battery
1013 // returns the value in seconds and percent or -1 if not available
1014 extern "C" int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1015 {
1016  LocalReferenceHolder refs(__FUNCTION__);
1017  JNIEnv* env = Android_JNI_GetEnv();
1018  if (!refs.init(env)) {
1019  return -1;
1020  }
1021 
1022  jmethodID mid;
1023 
1024  mid = env->GetStaticMethodID(mActivityClass, "getContext", "()Landroid/content/Context;");
1025  jobject context = env->CallStaticObjectMethod(mActivityClass, mid);
1026 
1027  jstring action = env->NewStringUTF("android.intent.action.BATTERY_CHANGED");
1028 
1029  jclass cls = env->FindClass("android/content/IntentFilter");
1030 
1031  mid = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
1032  jobject filter = env->NewObject(cls, mid, action);
1033 
1034  env->DeleteLocalRef(action);
1035 
1036  mid = env->GetMethodID(mActivityClass, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;");
1037  jobject intent = env->CallObjectMethod(context, mid, NULL, filter);
1038 
1039  env->DeleteLocalRef(filter);
1040 
1041  cls = env->GetObjectClass(intent);
1042 
1043  jstring iname;
1044  jmethodID imid = env->GetMethodID(cls, "getIntExtra", "(Ljava/lang/String;I)I");
1045 
1046 #define GET_INT_EXTRA(var, key) \
1047  iname = env->NewStringUTF(key); \
1048  int var = env->CallIntMethod(intent, imid, iname, -1); \
1049  env->DeleteLocalRef(iname);
1050 
1051  jstring bname;
1052  jmethodID bmid = env->GetMethodID(cls, "getBooleanExtra", "(Ljava/lang/String;Z)Z");
1053 
1054 #define GET_BOOL_EXTRA(var, key) \
1055  bname = env->NewStringUTF(key); \
1056  int var = env->CallBooleanMethod(intent, bmid, bname, JNI_FALSE); \
1057  env->DeleteLocalRef(bname);
1058 
1059  if (plugged) {
1060  GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
1061  if (plug == -1) {
1062  return -1;
1063  }
1064  // 1 == BatteryManager.BATTERY_PLUGGED_AC
1065  // 2 == BatteryManager.BATTERY_PLUGGED_USB
1066  *plugged = (0 < plug) ? 1 : 0;
1067  }
1068 
1069  if (charged) {
1070  GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
1071  if (status == -1) {
1072  return -1;
1073  }
1074  // 5 == BatteryManager.BATTERY_STATUS_FULL
1075  *charged = (status == 5) ? 1 : 0;
1076  }
1077 
1078  if (battery) {
1079  GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
1080  *battery = present ? 1 : 0;
1081  }
1082 
1083  if (seconds) {
1084  *seconds = -1; // not possible
1085  }
1086 
1087  if (percent) {
1088  GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
1089  GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
1090  if ((level == -1) || (scale == -1)) {
1091  return -1;
1092  }
1093  *percent = level * 100 / scale;
1094  }
1095 
1096  env->DeleteLocalRef(intent);
1097 
1098  return 0;
1099 }
1100 
1101 // sends message to be handled on the UI event dispatch thread
1102 extern "C" int Android_JNI_SendMessage(int command, int param)
1103 {
1104  JNIEnv *env = Android_JNI_GetEnv();
1105  if (!env) {
1106  return -1;
1107  }
1108  jmethodID mid = env->GetStaticMethodID(mActivityClass, "sendMessage", "(II)Z");
1109  if (!mid) {
1110  return -1;
1111  }
1112  jboolean success = env->CallStaticBooleanMethod(mActivityClass, mid, command, param);
1113  return success ? 0 : -1;
1114 }
1115 
1116 extern "C" void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
1117 {
1118  JNIEnv *env = Android_JNI_GetEnv();
1119  if (!env) {
1120  return;
1121  }
1122 
1123  jmethodID mid = env->GetStaticMethodID(mActivityClass, "showTextInput", "(IIII)Z");
1124  if (!mid) {
1125  return;
1126  }
1127  env->CallStaticBooleanMethod( mActivityClass, mid,
1128  inputRect->x,
1129  inputRect->y,
1130  inputRect->w,
1131  inputRect->h );
1132 }
1133 
1134 extern "C" void Android_JNI_HideTextInput()
1135 {
1136  // has to match Activity constant
1137  const int COMMAND_TEXTEDIT_HIDE = 3;
1138  Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1139 }
1140 
1142 //
1143 // Functions exposed to SDL applications in SDL_system.h
1144 //
1145 
1146 extern "C" void *SDL_AndroidGetJNIEnv()
1147 {
1148  return Android_JNI_GetEnv();
1149 }
1150 
1151 
1152 
1153 extern "C" void *SDL_AndroidGetActivity()
1154 {
1155  /* See SDL_system.h for caveats on using this function. */
1156 
1157  jmethodID mid;
1158 
1159  JNIEnv *env = Android_JNI_GetEnv();
1160  if (!env) {
1161  return NULL;
1162  }
1163 
1164  // return SDLActivity.getContext();
1165  mid = env->GetStaticMethodID(mActivityClass,
1166  "getContext","()Landroid/content/Context;");
1167  return env->CallStaticObjectMethod(mActivityClass, mid);
1168 }
1169 
1170 extern "C" const char * SDL_AndroidGetInternalStoragePath()
1171 {
1172  static char *s_AndroidInternalFilesPath = NULL;
1173 
1174  if (!s_AndroidInternalFilesPath) {
1175  LocalReferenceHolder refs(__FUNCTION__);
1176  jmethodID mid;
1177  jobject context;
1178  jobject fileObject;
1179  jstring pathString;
1180  const char *path;
1181 
1182  JNIEnv *env = Android_JNI_GetEnv();
1183  if (!refs.init(env)) {
1184  return NULL;
1185  }
1186 
1187  // context = SDLActivity.getContext();
1188  mid = env->GetStaticMethodID(mActivityClass,
1189  "getContext","()Landroid/content/Context;");
1190  context = env->CallStaticObjectMethod(mActivityClass, mid);
1191 
1192  // fileObj = context.getFilesDir();
1193  mid = env->GetMethodID(env->GetObjectClass(context),
1194  "getFilesDir", "()Ljava/io/File;");
1195  fileObject = env->CallObjectMethod(context, mid);
1196  if (!fileObject) {
1197  SDL_SetError("Couldn't get internal directory");
1198  return NULL;
1199  }
1200 
1201  // path = fileObject.getAbsolutePath();
1202  mid = env->GetMethodID(env->GetObjectClass(fileObject),
1203  "getAbsolutePath", "()Ljava/lang/String;");
1204  pathString = (jstring)env->CallObjectMethod(fileObject, mid);
1205 
1206  path = env->GetStringUTFChars(pathString, NULL);
1207  s_AndroidInternalFilesPath = SDL_strdup(path);
1208  env->ReleaseStringUTFChars(pathString, path);
1209  }
1210  return s_AndroidInternalFilesPath;
1211 }
1212 
1213 extern "C" int SDL_AndroidGetExternalStorageState()
1214 {
1215  LocalReferenceHolder refs(__FUNCTION__);
1216  jmethodID mid;
1217  jclass cls;
1218  jstring stateString;
1219  const char *state;
1220  int stateFlags;
1221 
1222  JNIEnv *env = Android_JNI_GetEnv();
1223  if (!refs.init(env)) {
1224  return 0;
1225  }
1226 
1227  cls = env->FindClass("android/os/Environment");
1228  mid = env->GetStaticMethodID(cls,
1229  "getExternalStorageState", "()Ljava/lang/String;");
1230  stateString = (jstring)env->CallStaticObjectMethod(cls, mid);
1231 
1232  state = env->GetStringUTFChars(stateString, NULL);
1233 
1234  // Print an info message so people debugging know the storage state
1235  __android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
1236 
1237  if (SDL_strcmp(state, "mounted") == 0) {
1238  stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ |
1239  SDL_ANDROID_EXTERNAL_STORAGE_WRITE;
1240  } else if (SDL_strcmp(state, "mounted_ro") == 0) {
1241  stateFlags = SDL_ANDROID_EXTERNAL_STORAGE_READ;
1242  } else {
1243  stateFlags = 0;
1244  }
1245  env->ReleaseStringUTFChars(stateString, state);
1246 
1247  return stateFlags;
1248 }
1249 
1250 extern "C" const char * SDL_AndroidGetExternalStoragePath()
1251 {
1252  static char *s_AndroidExternalFilesPath = NULL;
1253 
1254  if (!s_AndroidExternalFilesPath) {
1255  LocalReferenceHolder refs(__FUNCTION__);
1256  jmethodID mid;
1257  jobject context;
1258  jobject fileObject;
1259  jstring pathString;
1260  const char *path;
1261 
1262  JNIEnv *env = Android_JNI_GetEnv();
1263  if (!refs.init(env)) {
1264  return NULL;
1265  }
1266 
1267  // context = SDLActivity.getContext();
1268  mid = env->GetStaticMethodID(mActivityClass,
1269  "getContext","()Landroid/content/Context;");
1270  context = env->CallStaticObjectMethod(mActivityClass, mid);
1271 
1272  // fileObj = context.getExternalFilesDir();
1273  mid = env->GetMethodID(env->GetObjectClass(context),
1274  "getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
1275  fileObject = env->CallObjectMethod(context, mid, NULL);
1276  if (!fileObject) {
1277  SDL_SetError("Couldn't get external directory");
1278  return NULL;
1279  }
1280 
1281  // path = fileObject.getAbsolutePath();
1282  mid = env->GetMethodID(env->GetObjectClass(fileObject),
1283  "getAbsolutePath", "()Ljava/lang/String;");
1284  pathString = (jstring)env->CallObjectMethod(fileObject, mid);
1285 
1286  path = env->GetStringUTFChars(pathString, NULL);
1287  s_AndroidExternalFilesPath = SDL_strdup(path);
1288  env->ReleaseStringUTFChars(pathString, path);
1289  }
1290  return s_AndroidExternalFilesPath;
1291 }
1292 
1293 #endif /* __ANDROID__ */
1294 
1295 /* vi: set ts=4 sw=4 expandtab: */
return
Definition: pngrutil.c:1266
int Android_JNI_FileClose(SDL_RWops *ctx)
GLenum GLint param
Definition: gl2ext.h:1491
int Android_OnKeyUp(int keycode)
#define EGL_RED_SIZE
Definition: egl.h:100
int Android_JNI_SendMessage(int command, int param)
void Android_JNI_ShowTextInput(SDL_Rect *inputRect)
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: gl2ext.h:961
#define NULL
Definition: ftobjs.h:61
GLenum GLsizei const void * pathString
Definition: glew.h:12419
SDL_bool Android_JNI_HasClipboardText()
SDL_bool
Definition: SDL_stdinc.h:116
SDL_bool Android_JNI_GetAccelerometerValues(float values[3])
GLint GLfloat GLint stencil
Definition: SDL_opengl.h:5886
EGLSurface EGLint x
Definition: eglext.h:293
#define EGL_SAMPLE_BUFFERS
Definition: egl.h:113
static void init(struct bs2b *bs2b)
Definition: bs2b.c:46
void Android_JNI_SetActivityTitle(const char *title)
void size_t size
Definition: SDL_rwops.h:74
char * Android_JNI_GetClipboardText()
#define EGL_ALPHA_SIZE
Definition: egl.h:97
#define EGL_NONE
Definition: egl.h:119
int Android_OnKeyDown(int keycode)
EGLSurface EGLint EGLint EGLint EGLint height
Definition: eglext.h:293
void Android_OnTouch(int touch_device_id_in, int pointer_finger_id_in, int action, float x, float y, float p)
EGLImageKHR EGLint * name
Definition: eglext.h:284
#define RW_SEEK_END
Definition: SDL_rwops.h:176
GLclampf green
Definition: glew.h:1506
void Android_JNI_HideTextInput()
int SDL_SendWindowEvent(SDL_Window *window, Uint8 windowevent, int data1, int data2)
GLenum GLsizei len
Definition: glew.h:7035
if(!yyg->yy_init)
GLenum func
Definition: SDL_opengl.h:5654
const GLint * attribs
Definition: glew.h:13064
GLsizei const GLchar *const * path
Definition: glew.h:5828
int Android_JNI_FileOpen(SDL_RWops *ctx, const char *fileName, const char *mode)
ret
Definition: glew_str_glx.c:2
GLenum array
Definition: glew.h:8327
#define EGL_BLUE_SIZE
Definition: egl.h:98
const GLubyte GLuint red
Definition: SDL_glfuncs.h:57
SDL_sem * Android_PauseSem
const GLuint * buffers
Definition: glew.h:1669
int Android_JNI_SetupThread(void)
EGLContext EGLenum EGLClientBuffer buffer
Definition: eglext.h:87
#define EGL_BUFFER_SIZE
Definition: egl.h:96
GLuint64EXT * result
Definition: glew.h:12708
#define EGL_OPENGL_ES_BIT
Definition: egl.h:155
EGLSurface EGLint EGLint EGLint width
Definition: eglext.h:293
int SDL_SendKeyboardText(const char *text)
Definition: SDL_keyboard.c:784
DECLSPEC char *SDLCALL SDL_strdup(const char *str)
Definition: SDL_string.c:511
DECLSPEC int SDLCALL SDL_Error(SDL_errorcode code)
Definition: SDL_error.c:222
DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem *sem)
Definition: SDL_syssem.c:186
GLfloat GLfloat p
Definition: glew.h:14938
GLsizei samples
Definition: gl2ext.h:970
void Android_JNI_CloseAudioDevice()
DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops *area)
Definition: SDL_rwops.c:637
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
GLint GLenum GLsizei GLsizei GLsizei GLint GLenum format
Definition: gl2ext.h:845
DECLSPEC int SDLCALL SDL_SetError(const char *fmt,...)
Definition: SDL_error.c:53
void Android_SetScreenResolution(int width, int height, Uint32 format)
GLint GLenum GLsizei GLsizei GLsizei depth
Definition: gl2ext.h:845
DECLSPEC int SDLCALL SDL_strcmp(const char *str1, const char *str2)
Definition: SDL_string.c:910
int x
Definition: SDL_rect.h:65
GLclampf GLclampf GLclampf alpha
Definition: glew.h:1506
int w
Definition: SDL_rect.h:66
#define EGL_DEPTH_SIZE
Definition: egl.h:101
jmp_buf env
Definition: jumphack.c:12
GLenum GLenum GLenum GLenum GLenum scale
Definition: glew.h:12632
void * Android_JNI_GetAudioBuffer()
GLuint num
Definition: glew.h:2631
#define SEEK_SET
Definition: zconf.h:249
#define SDL_assert(condition)
Definition: SDL_assert.h:159
#define EGL_SAMPLES
Definition: egl.h:112
EGLSurface EGLint EGLint y
Definition: eglext.h:293
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:83
DECLSPEC void SDLCALL SDL_Log(const char *fmt,...)
Log a message with SDL_LOG_CATEGORY_APPLICATION and SDL_LOG_PRIORITY_INFO.
Definition: SDL_log.c:173
EGLSurface EGLint void ** value
Definition: eglext.h:301
GLintptr offset
Definition: glew.h:1668
SDL_sem * Android_ResumeSem
size_t Android_JNI_FileRead(SDL_RWops *ctx, void *buffer, size_t size, size_t maxnum)
void Android_JNI_WriteAudioBuffer()
int h
Definition: SDL_rect.h:66
union SDL_RWops::@74 hidden
Sint64 offset
Definition: SDL_rwops.h:65
GLint level
Definition: gl2ext.h:845
#define EGL_GREEN_SIZE
Definition: egl.h:99
int Android_JNI_SetClipboardText(const char *text)
#define RW_SEEK_SET
Definition: SDL_rwops.h:174
#define EGL_STENCIL_SIZE
Definition: egl.h:102
GLint GLint GLint GLint z
Definition: gl2ext.h:1214
int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
int SDL_SendAppEvent(SDL_EventType eventType)
Definition: SDL_events.c:610
#define RW_SEEK_CUR
Definition: SDL_rwops.h:175
TParseContext * context
int i
Definition: pngrutil.c:1377
GLclampf GLclampf blue
Definition: glew.h:1506
DECLSPEC int SDLCALL SDL_SemPost(SDL_sem *sem)
Definition: SDL_syssem.c:200
int64_t Sint64
A signed 64-bit integer type.
Definition: SDL_stdinc.h:150
#define EGL_RENDERABLE_TYPE
Definition: egl.h:127
int y
Definition: SDL_rect.h:65
JNIEnv * Android_JNI_GetEnv(void)
int SDL_SendEditingText(const char *text, int start, int length)
Definition: SDL_keyboard.c:807
Sint64 Android_JNI_FileSeek(SDL_RWops *ctx, Sint64 offset, int whence)
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:63
GLint GLsizei const GLuint64 * values
Definition: glew.h:3473
void Android_JNI_SwapWindow()
int SDL_SendQuit(void)
Definition: SDL_quit.c:115
#define EGL_OPENGL_ES2_BIT
Definition: egl.h:157
Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
GLsizei size
Definition: gl2ext.h:1467
EGLContext ctx
Definition: eglext.h:87