* Android's InputStream::skip is apparently buggy, so instead read into a dummy buffer
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21 #include "SDL_config.h"
22 #include "SDL_stdinc.h"
24 #include "SDL_android.h"
29 #include "../../events/SDL_events_c.h"
30 #include "../../video/android/SDL_androidkeyboard.h"
31 #include "../../video/android/SDL_androidtouch.h"
32 #include "../../video/android/SDL_androidvideo.h"
34 /* Impelemented in audio/android/SDL_androidaudio.c */
35 extern void Android_RunAudioThread();
38 /*******************************************************************************
39 This file links the Java side of Android with libsdl
40 *******************************************************************************/
42 #include <android/log.h>
45 /*******************************************************************************
47 *******************************************************************************/
48 static JNIEnv* mEnv = NULL;
49 static JNIEnv* mAudioEnv = NULL;
52 static jclass mActivityClass;
55 static jmethodID midCreateGLContext;
56 static jmethodID midFlipBuffers;
57 static jmethodID midAudioInit;
58 static jmethodID midAudioWriteShortBuffer;
59 static jmethodID midAudioWriteByteBuffer;
60 static jmethodID midAudioQuit;
62 // Accelerometer data storage
63 static float fLastAccelerometer[3];
66 /*******************************************************************************
67 Functions called by JNI
68 *******************************************************************************/
71 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
73 return JNI_VERSION_1_4;
76 // Called before SDL_main() to initialize JNI bindings
77 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
79 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
84 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
85 "createGLContext","(II)Z");
86 midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
88 midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
89 "audioInit", "(IZZI)Ljava/lang/Object;");
90 midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
91 "audioWriteShortBuffer", "([S)V");
92 midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
93 "audioWriteByteBuffer", "([B)V");
94 midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
97 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
98 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
99 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
104 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
105 JNIEnv* env, jclass jcls,
106 jint width, jint height, jint format)
108 Android_SetScreenResolution(width, height, format);
112 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
113 JNIEnv* env, jclass jcls, jint keycode)
115 Android_OnKeyDown(keycode);
119 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
120 JNIEnv* env, jclass jcls, jint keycode)
122 Android_OnKeyUp(keycode);
126 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
127 JNIEnv* env, jclass jcls,
128 jint touch_device_id_in, jint pointer_finger_id_in,
129 jint action, jfloat x, jfloat y, jfloat p)
131 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
135 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
136 JNIEnv* env, jclass jcls,
137 jfloat x, jfloat y, jfloat z)
139 fLastAccelerometer[0] = x;
140 fLastAccelerometer[1] = y;
141 fLastAccelerometer[2] = z;
145 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
146 JNIEnv* env, jclass cls)
148 // Inject a SDL_QUIT event
152 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
153 JNIEnv* env, jclass cls)
155 /* This is the audio thread, with a different environment */
158 Android_RunAudioThread();
162 /*******************************************************************************
163 Functions called by SDL into Java
164 *******************************************************************************/
165 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
167 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
174 extern "C" void Android_JNI_SwapWindow()
176 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
179 extern "C" void Android_JNI_SetActivityTitle(const char *title)
183 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
185 mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
189 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
192 for (i = 0; i < 3; ++i) {
193 values[i] = fLastAccelerometer[i];
200 static jboolean audioBuffer16Bit = JNI_FALSE;
201 static jboolean audioBufferStereo = JNI_FALSE;
202 static jobject audioBuffer = NULL;
203 static void* audioBufferPinned = NULL;
205 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
207 int audioBufferFrames;
209 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
210 audioBuffer16Bit = is16Bit;
211 audioBufferStereo = channelCount > 1;
213 audioBuffer = mEnv->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
215 if (audioBuffer == NULL) {
216 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
219 audioBuffer = mEnv->NewGlobalRef(audioBuffer);
221 jboolean isCopy = JNI_FALSE;
222 if (audioBuffer16Bit) {
223 audioBufferPinned = mEnv->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
224 audioBufferFrames = mEnv->GetArrayLength((jshortArray)audioBuffer);
226 audioBufferPinned = mEnv->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
227 audioBufferFrames = mEnv->GetArrayLength((jbyteArray)audioBuffer);
229 if (audioBufferStereo) {
230 audioBufferFrames /= 2;
233 return audioBufferFrames;
236 extern "C" void * Android_JNI_GetAudioBuffer()
238 return audioBufferPinned;
241 extern "C" void Android_JNI_WriteAudioBuffer()
243 if (audioBuffer16Bit) {
244 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
245 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
247 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
248 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
251 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
254 extern "C" void Android_JNI_CloseAudioDevice()
256 mEnv->CallStaticVoidMethod(mActivityClass, midAudioQuit);
259 mEnv->DeleteGlobalRef(audioBuffer);
261 audioBufferPinned = NULL;
265 // Test for an exception and call SDL_SetError with its detail if one occurs
266 static bool Android_JNI_ExceptionOccurred()
268 jthrowable exception = mEnv->ExceptionOccurred();
269 if (exception != NULL) {
272 // Until this happens most JNI operations have undefined behaviour
273 mEnv->ExceptionClear();
275 jclass exceptionClass = mEnv->GetObjectClass(exception);
276 jclass classClass = mEnv->FindClass("java/lang/Class");
278 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
279 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
280 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
282 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
283 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
285 if (exceptionMessage != NULL) {
286 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
287 exceptionMessage, 0);
288 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
289 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
290 mEnv->DeleteLocalRef(exceptionMessage);
292 SDL_SetError("%s", exceptionNameUTF8);
295 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
296 mEnv->DeleteLocalRef(exceptionName);
297 mEnv->DeleteLocalRef(classClass);
298 mEnv->DeleteLocalRef(exceptionClass);
299 mEnv->DeleteLocalRef(exception);
307 static int Android_JNI_FileOpen(SDL_RWops* ctx)
313 jobject assetManager;
316 jobject readableByteChannel;
317 jstring fileNameJString;
319 bool allocatedLocalFrame = false;
321 if (mEnv->PushLocalFrame(16) < 0) {
322 SDL_SetError("Failed to allocate enough JVM local references");
325 allocatedLocalFrame = true;
328 fileNameJString = (jstring)ctx->hidden.androidio.fileName;
330 // context = SDLActivity.getContext();
331 mid = mEnv->GetStaticMethodID(mActivityClass,
332 "getContext","()Landroid/content/Context;");
333 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
335 // assetManager = context.getAssets();
336 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
337 "getAssets", "()Landroid/content/res/AssetManager;");
338 assetManager = mEnv->CallObjectMethod(context, mid);
340 // inputStream = assetManager.open(<filename>);
341 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
342 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
343 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
344 if (Android_JNI_ExceptionOccurred()) {
348 ctx->hidden.androidio.inputStream = inputStream;
349 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
351 // Despite all the visible documentation on [Asset]InputStream claiming
352 // that the .available() method is not guaranteed to return the entire file
353 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
354 // android/apis/content/ReadAsset.java imply that Android's
355 // AssetInputStream.available() /will/ always return the total file size
357 // size = inputStream.available();
358 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
360 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
361 if (Android_JNI_ExceptionOccurred()) {
365 // readableByteChannel = Channels.newChannel(inputStream);
366 channels = mEnv->FindClass("java/nio/channels/Channels");
367 mid = mEnv->GetStaticMethodID(channels,
369 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
370 readableByteChannel = mEnv->CallStaticObjectMethod(
371 channels, mid, inputStream);
372 if (Android_JNI_ExceptionOccurred()) {
376 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
377 ctx->hidden.androidio.readableByteChannelRef =
378 mEnv->NewGlobalRef(readableByteChannel);
380 // Store .read id for reading purposes
381 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
382 "read", "(Ljava/nio/ByteBuffer;)I");
383 ctx->hidden.androidio.readMethod = mid;
385 ctx->hidden.androidio.position = 0;
391 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
393 if(ctx->hidden.androidio.inputStreamRef != NULL) {
394 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
398 if (allocatedLocalFrame) {
399 mEnv->PopLocalFrame(NULL);
405 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
406 const char* fileName, const char*)
412 jstring fileNameJString = mEnv->NewStringUTF(fileName);
413 ctx->hidden.androidio.fileName = fileNameJString;
414 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
415 ctx->hidden.androidio.inputStreamRef = NULL;
416 mEnv->DeleteLocalRef(fileNameJString);
418 return Android_JNI_FileOpen(ctx);
421 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
422 size_t size, size_t maxnum)
424 int bytesRemaining = size * maxnum;
427 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
428 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
429 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
431 while (bytesRemaining > 0) {
432 // result = readableByteChannel.read(...);
433 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
435 if (Android_JNI_ExceptionOccurred()) {
436 mEnv->DeleteLocalRef(byteBuffer);
444 bytesRemaining -= result;
446 ctx->hidden.androidio.position += result;
449 mEnv->DeleteLocalRef(byteBuffer);
451 return bytesRead / size;
454 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
455 size_t size, size_t num)
457 SDL_SetError("Cannot write to Android package filesystem");
461 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
467 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
470 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
472 // inputStream.close();
473 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
475 mEnv->CallVoidMethod(inputStream, mid);
476 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
477 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
478 if (Android_JNI_ExceptionOccurred()) {
491 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
497 newPosition = offset;
500 newPosition = ctx->hidden.androidio.position + offset;
503 newPosition = ctx->hidden.androidio.size + offset;
506 SDL_SetError("Unknown value for 'whence'");
509 if (newPosition < 0) {
512 if (newPosition > ctx->hidden.androidio.size) {
513 newPosition = ctx->hidden.androidio.size;
516 long movement = newPosition - ctx->hidden.androidio.position;
517 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
520 unsigned char buffer[1024];
522 // The easy case where we're seeking forwards
523 while (movement > 0) {
524 size_t result = Android_JNI_FileRead(ctx, buffer, 1,
525 std::min(movement, (long)sizeof(buffer)));
528 // Failed to read/skip the required amount, so fail
534 } else if (movement < 0) {
535 // We can't seek backwards so we have to reopen the file and seek
536 // forwards which obviously isn't very efficient
537 Android_JNI_FileClose(ctx, false);
538 Android_JNI_FileOpen(ctx);
539 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
542 ctx->hidden.androidio.position = newPosition;
544 return ctx->hidden.androidio.position;
547 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
549 return Android_JNI_FileClose(ctx, true);
552 /* vi: set ts=4 sw=4 expandtab: */