Removed STL dependency in Android code.
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"
27 #include "../../events/SDL_events_c.h"
28 #include "../../video/android/SDL_androidkeyboard.h"
29 #include "../../video/android/SDL_androidtouch.h"
30 #include "../../video/android/SDL_androidvideo.h"
32 /* Impelemented in audio/android/SDL_androidaudio.c */
33 extern void Android_RunAudioThread();
36 /*******************************************************************************
37 This file links the Java side of Android with libsdl
38 *******************************************************************************/
40 #include <android/log.h>
43 /*******************************************************************************
45 *******************************************************************************/
46 static JNIEnv* mEnv = NULL;
47 static JNIEnv* mAudioEnv = NULL;
50 static jclass mActivityClass;
53 static jmethodID midCreateGLContext;
54 static jmethodID midFlipBuffers;
55 static jmethodID midAudioInit;
56 static jmethodID midAudioWriteShortBuffer;
57 static jmethodID midAudioWriteByteBuffer;
58 static jmethodID midAudioQuit;
60 // Accelerometer data storage
61 static float fLastAccelerometer[3];
64 /*******************************************************************************
65 Functions called by JNI
66 *******************************************************************************/
69 extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
71 return JNI_VERSION_1_4;
74 // Called before SDL_main() to initialize JNI bindings
75 extern "C" void SDL_Android_Init(JNIEnv* env, jclass cls)
77 __android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init()");
82 midCreateGLContext = mEnv->GetStaticMethodID(mActivityClass,
83 "createGLContext","(II)Z");
84 midFlipBuffers = mEnv->GetStaticMethodID(mActivityClass,
86 midAudioInit = mEnv->GetStaticMethodID(mActivityClass,
87 "audioInit", "(IZZI)Ljava/lang/Object;");
88 midAudioWriteShortBuffer = mEnv->GetStaticMethodID(mActivityClass,
89 "audioWriteShortBuffer", "([S)V");
90 midAudioWriteByteBuffer = mEnv->GetStaticMethodID(mActivityClass,
91 "audioWriteByteBuffer", "([B)V");
92 midAudioQuit = mEnv->GetStaticMethodID(mActivityClass,
95 if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
96 !midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
97 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
102 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeResize(
103 JNIEnv* env, jclass jcls,
104 jint width, jint height, jint format)
106 Android_SetScreenResolution(width, height, format);
110 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
111 JNIEnv* env, jclass jcls, jint keycode)
113 Android_OnKeyDown(keycode);
117 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
118 JNIEnv* env, jclass jcls, jint keycode)
120 Android_OnKeyUp(keycode);
124 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeTouch(
125 JNIEnv* env, jclass jcls,
126 jint touch_device_id_in, jint pointer_finger_id_in,
127 jint action, jfloat x, jfloat y, jfloat p)
129 Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
133 extern "C" void Java_org_libsdl_app_SDLActivity_onNativeAccel(
134 JNIEnv* env, jclass jcls,
135 jfloat x, jfloat y, jfloat z)
137 fLastAccelerometer[0] = x;
138 fLastAccelerometer[1] = y;
139 fLastAccelerometer[2] = z;
143 extern "C" void Java_org_libsdl_app_SDLActivity_nativeQuit(
144 JNIEnv* env, jclass cls)
146 // Inject a SDL_QUIT event
150 extern "C" void Java_org_libsdl_app_SDLActivity_nativeRunAudioThread(
151 JNIEnv* env, jclass cls)
153 /* This is the audio thread, with a different environment */
156 Android_RunAudioThread();
160 /*******************************************************************************
161 Functions called by SDL into Java
162 *******************************************************************************/
163 extern "C" SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion)
165 if (mEnv->CallStaticBooleanMethod(mActivityClass, midCreateGLContext, majorVersion, minorVersion)) {
172 extern "C" void Android_JNI_SwapWindow()
174 mEnv->CallStaticVoidMethod(mActivityClass, midFlipBuffers);
177 extern "C" void Android_JNI_SetActivityTitle(const char *title)
181 mid = mEnv->GetStaticMethodID(mActivityClass,"setActivityTitle","(Ljava/lang/String;)V");
183 mEnv->CallStaticVoidMethod(mActivityClass, mid, mEnv->NewStringUTF(title));
187 extern "C" void Android_JNI_GetAccelerometerValues(float values[3])
190 for (i = 0; i < 3; ++i) {
191 values[i] = fLastAccelerometer[i];
198 static jboolean audioBuffer16Bit = JNI_FALSE;
199 static jboolean audioBufferStereo = JNI_FALSE;
200 static jobject audioBuffer = NULL;
201 static void* audioBufferPinned = NULL;
203 extern "C" int Android_JNI_OpenAudioDevice(int sampleRate, int is16Bit, int channelCount, int desiredBufferFrames)
205 int audioBufferFrames;
207 __android_log_print(ANDROID_LOG_VERBOSE, "SDL", "SDL audio: opening device");
208 audioBuffer16Bit = is16Bit;
209 audioBufferStereo = channelCount > 1;
211 audioBuffer = mEnv->CallStaticObjectMethod(mActivityClass, midAudioInit, sampleRate, audioBuffer16Bit, audioBufferStereo, desiredBufferFrames);
213 if (audioBuffer == NULL) {
214 __android_log_print(ANDROID_LOG_WARN, "SDL", "SDL audio: didn't get back a good audio buffer!");
217 audioBuffer = mEnv->NewGlobalRef(audioBuffer);
219 jboolean isCopy = JNI_FALSE;
220 if (audioBuffer16Bit) {
221 audioBufferPinned = mEnv->GetShortArrayElements((jshortArray)audioBuffer, &isCopy);
222 audioBufferFrames = mEnv->GetArrayLength((jshortArray)audioBuffer);
224 audioBufferPinned = mEnv->GetByteArrayElements((jbyteArray)audioBuffer, &isCopy);
225 audioBufferFrames = mEnv->GetArrayLength((jbyteArray)audioBuffer);
227 if (audioBufferStereo) {
228 audioBufferFrames /= 2;
231 return audioBufferFrames;
234 extern "C" void * Android_JNI_GetAudioBuffer()
236 return audioBufferPinned;
239 extern "C" void Android_JNI_WriteAudioBuffer()
241 if (audioBuffer16Bit) {
242 mAudioEnv->ReleaseShortArrayElements((jshortArray)audioBuffer, (jshort *)audioBufferPinned, JNI_COMMIT);
243 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteShortBuffer, (jshortArray)audioBuffer);
245 mAudioEnv->ReleaseByteArrayElements((jbyteArray)audioBuffer, (jbyte *)audioBufferPinned, JNI_COMMIT);
246 mAudioEnv->CallStaticVoidMethod(mActivityClass, midAudioWriteByteBuffer, (jbyteArray)audioBuffer);
249 /* JNI_COMMIT means the changes are committed to the VM but the buffer remains pinned */
252 extern "C" void Android_JNI_CloseAudioDevice()
254 mEnv->CallStaticVoidMethod(mActivityClass, midAudioQuit);
257 mEnv->DeleteGlobalRef(audioBuffer);
259 audioBufferPinned = NULL;
263 // Test for an exception and call SDL_SetError with its detail if one occurs
264 static bool Android_JNI_ExceptionOccurred()
266 jthrowable exception = mEnv->ExceptionOccurred();
267 if (exception != NULL) {
270 // Until this happens most JNI operations have undefined behaviour
271 mEnv->ExceptionClear();
273 jclass exceptionClass = mEnv->GetObjectClass(exception);
274 jclass classClass = mEnv->FindClass("java/lang/Class");
276 mid = mEnv->GetMethodID(classClass, "getName", "()Ljava/lang/String;");
277 jstring exceptionName = (jstring)mEnv->CallObjectMethod(exceptionClass, mid);
278 const char* exceptionNameUTF8 = mEnv->GetStringUTFChars(exceptionName, 0);
280 mid = mEnv->GetMethodID(exceptionClass, "getMessage", "()Ljava/lang/String;");
281 jstring exceptionMessage = (jstring)mEnv->CallObjectMethod(exception, mid);
283 if (exceptionMessage != NULL) {
284 const char* exceptionMessageUTF8 = mEnv->GetStringUTFChars(
285 exceptionMessage, 0);
286 SDL_SetError("%s: %s", exceptionNameUTF8, exceptionMessageUTF8);
287 mEnv->ReleaseStringUTFChars(exceptionMessage, exceptionMessageUTF8);
288 mEnv->DeleteLocalRef(exceptionMessage);
290 SDL_SetError("%s", exceptionNameUTF8);
293 mEnv->ReleaseStringUTFChars(exceptionName, exceptionNameUTF8);
294 mEnv->DeleteLocalRef(exceptionName);
295 mEnv->DeleteLocalRef(classClass);
296 mEnv->DeleteLocalRef(exceptionClass);
297 mEnv->DeleteLocalRef(exception);
305 static int Android_JNI_FileOpen(SDL_RWops* ctx)
311 jobject assetManager;
314 jobject readableByteChannel;
315 jstring fileNameJString;
317 bool allocatedLocalFrame = false;
319 if (mEnv->PushLocalFrame(16) < 0) {
320 SDL_SetError("Failed to allocate enough JVM local references");
323 allocatedLocalFrame = true;
326 fileNameJString = (jstring)ctx->hidden.androidio.fileName;
328 // context = SDLActivity.getContext();
329 mid = mEnv->GetStaticMethodID(mActivityClass,
330 "getContext","()Landroid/content/Context;");
331 context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
333 // assetManager = context.getAssets();
334 mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
335 "getAssets", "()Landroid/content/res/AssetManager;");
336 assetManager = mEnv->CallObjectMethod(context, mid);
338 // inputStream = assetManager.open(<filename>);
339 mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
340 "open", "(Ljava/lang/String;)Ljava/io/InputStream;");
341 inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
342 if (Android_JNI_ExceptionOccurred()) {
346 ctx->hidden.androidio.inputStream = inputStream;
347 ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
349 // Despite all the visible documentation on [Asset]InputStream claiming
350 // that the .available() method is not guaranteed to return the entire file
351 // size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
352 // android/apis/content/ReadAsset.java imply that Android's
353 // AssetInputStream.available() /will/ always return the total file size
355 // size = inputStream.available();
356 mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
358 ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
359 if (Android_JNI_ExceptionOccurred()) {
363 // readableByteChannel = Channels.newChannel(inputStream);
364 channels = mEnv->FindClass("java/nio/channels/Channels");
365 mid = mEnv->GetStaticMethodID(channels,
367 "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
368 readableByteChannel = mEnv->CallStaticObjectMethod(
369 channels, mid, inputStream);
370 if (Android_JNI_ExceptionOccurred()) {
374 ctx->hidden.androidio.readableByteChannel = readableByteChannel;
375 ctx->hidden.androidio.readableByteChannelRef =
376 mEnv->NewGlobalRef(readableByteChannel);
378 // Store .read id for reading purposes
379 mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
380 "read", "(Ljava/nio/ByteBuffer;)I");
381 ctx->hidden.androidio.readMethod = mid;
383 ctx->hidden.androidio.position = 0;
389 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
391 if(ctx->hidden.androidio.inputStreamRef != NULL) {
392 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
396 if (allocatedLocalFrame) {
397 mEnv->PopLocalFrame(NULL);
403 extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
404 const char* fileName, const char*)
410 jstring fileNameJString = mEnv->NewStringUTF(fileName);
411 ctx->hidden.androidio.fileName = fileNameJString;
412 ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
413 ctx->hidden.androidio.inputStreamRef = NULL;
414 mEnv->DeleteLocalRef(fileNameJString);
416 return Android_JNI_FileOpen(ctx);
419 extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
420 size_t size, size_t maxnum)
422 int bytesRemaining = size * maxnum;
425 jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
426 jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
427 jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
429 while (bytesRemaining > 0) {
430 // result = readableByteChannel.read(...);
431 int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
433 if (Android_JNI_ExceptionOccurred()) {
434 mEnv->DeleteLocalRef(byteBuffer);
442 bytesRemaining -= result;
444 ctx->hidden.androidio.position += result;
447 mEnv->DeleteLocalRef(byteBuffer);
449 return bytesRead / size;
452 extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
453 size_t size, size_t num)
455 SDL_SetError("Cannot write to Android package filesystem");
459 static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
465 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
468 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
470 // inputStream.close();
471 jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
473 mEnv->CallVoidMethod(inputStream, mid);
474 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
475 mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
476 if (Android_JNI_ExceptionOccurred()) {
489 extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
495 newPosition = offset;
498 newPosition = ctx->hidden.androidio.position + offset;
501 newPosition = ctx->hidden.androidio.size + offset;
504 SDL_SetError("Unknown value for 'whence'");
507 if (newPosition < 0) {
510 if (newPosition > ctx->hidden.androidio.size) {
511 newPosition = ctx->hidden.androidio.size;
514 long movement = newPosition - ctx->hidden.androidio.position;
515 jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
518 unsigned char buffer[1024];
520 // The easy case where we're seeking forwards
521 while (movement > 0) {
522 long amount = (long) sizeof (buffer);
523 if (amount > movement) {
526 size_t result = Android_JNI_FileRead(ctx, buffer, 1, amount);
529 // Failed to read/skip the required amount, so fail
535 } else if (movement < 0) {
536 // We can't seek backwards so we have to reopen the file and seek
537 // forwards which obviously isn't very efficient
538 Android_JNI_FileClose(ctx, false);
539 Android_JNI_FileOpen(ctx);
540 Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
543 ctx->hidden.androidio.position = newPosition;
545 return ctx->hidden.androidio.position;
548 extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
550 return Android_JNI_FileClose(ctx, true);
553 /* vi: set ts=4 sw=4 expandtab: */