33
33
#include "../../video/android/SDL_androidkeyboard.h"
34
34
#include "../../video/android/SDL_androidtouch.h"
35
35
#include "../../video/android/SDL_androidvideo.h"
36
#include "../../video/android/SDL_androidwindow.h"
37
38
#include <android/log.h>
38
39
#include <pthread.h>
39
40
#include <sys/types.h>
40
41
#include <unistd.h>
41
42
#define LOG_TAG "SDL_android"
42
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
43
//#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
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__) */
44
45
#define LOGI(...) do {} while (false)
45
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
static void Android_JNI_ThreadDestroyed(void*);
63
64
static pthread_key_t mThreadKey;
64
65
static JavaVM* mJavaVM;
67
68
static jclass mActivityClass;
70
static jmethodID midCreateGLContext;
71
static jmethodID midDeleteGLContext;
70
/* method signatures */
71
static jmethodID midGetNativeSurface;
72
72
static jmethodID midFlipBuffers;
73
73
static jmethodID midAudioInit;
74
74
static jmethodID midAudioWriteShortBuffer;
75
75
static jmethodID midAudioWriteByteBuffer;
76
76
static jmethodID midAudioQuit;
78
// Accelerometer data storage
78
/* Accelerometer data storage */
79
79
static float fLastAccelerometer[3];
80
80
static bool bHasNewData;
117
117
mActivityClass = (jclass)((*mEnv)->NewGlobalRef(mEnv, cls));
119
midCreateGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
120
"createGLContext","(II[I)Z");
121
midDeleteGLContext = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
122
"deleteGLContext","()V");
119
midGetNativeSurface = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
120
"getNativeSurface","()Landroid/view/Surface;");
123
121
midFlipBuffers = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
124
122
"flipBuffers","()V");
125
123
midAudioInit = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
134
132
bHasNewData = false;
136
if(!midCreateGLContext || !midFlipBuffers || !midAudioInit ||
134
if(!midGetNativeSurface || !midFlipBuffers || !midAudioInit ||
137
135
!midAudioWriteShortBuffer || !midAudioWriteByteBuffer || !midAudioQuit) {
138
136
__android_log_print(ANDROID_LOG_WARN, "SDL", "SDL: Couldn't locate Java callbacks, check that they're named and typed correctly");
140
138
__android_log_print(ANDROID_LOG_INFO, "SDL", "SDL_Android_Init() finished!");
144
142
void Java_org_libsdl_app_SDLActivity_onNativeResize(
145
143
JNIEnv* env, jclass jcls,
146
144
jint width, jint height, jint format)
148
146
Android_SetScreenResolution(width, height, format);
150
/* Surface Created */
151
void Java_org_libsdl_app_SDLActivity_onNativeSurfaceChanged(JNIEnv* env, jclass jcls)
153
SDL_WindowData *data;
154
SDL_VideoDevice *_this;
156
if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
160
_this = SDL_GetVideoDevice();
161
data = (SDL_WindowData *) Android_Window->driverdata;
163
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
164
if (data->egl_surface == EGL_NO_SURFACE) {
165
if(data->native_window) {
166
ANativeWindow_release(data->native_window);
168
data->native_window = Android_JNI_GetNativeWindow();
169
data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->native_window);
172
/* GL Context handling is done in the event loop because this function is run from the Java thread */
176
/* Surface Destroyed */
177
void Java_org_libsdl_app_SDLActivity_onNativeSurfaceDestroyed(JNIEnv* env, jclass jcls)
179
/* We have to clear the current context and destroy the egl surface here
180
* Otherwise there's BAD_NATIVE_WINDOW errors coming from eglCreateWindowSurface on resume
181
* Ref: http://stackoverflow.com/questions/8762589/eglcreatewindowsurface-on-ics-and-switching-from-2d-to-3d
183
SDL_WindowData *data;
184
SDL_VideoDevice *_this;
186
if (Android_Window == NULL || Android_Window->driverdata == NULL ) {
190
_this = SDL_GetVideoDevice();
191
data = (SDL_WindowData *) Android_Window->driverdata;
193
if (data->egl_surface != EGL_NO_SURFACE) {
194
SDL_EGL_MakeCurrent(_this, NULL, NULL);
195
SDL_EGL_DestroySurface(_this, data->egl_surface);
196
data->egl_surface = EGL_NO_SURFACE;
199
/* GL Context handling is done in the event loop because this function is run from the Java thread */
203
void Java_org_libsdl_app_SDLActivity_nativeFlipBuffers(JNIEnv* env, jclass jcls)
205
SDL_GL_SwapWindow(Android_Window);
152
209
void Java_org_libsdl_app_SDLActivity_onNativeKeyDown(
153
210
JNIEnv* env, jclass jcls, jint keycode)
155
212
Android_OnKeyDown(keycode);
159
216
void Java_org_libsdl_app_SDLActivity_onNativeKeyUp(
160
217
JNIEnv* env, jclass jcls, jint keycode)
162
219
Android_OnKeyUp(keycode);
165
// Keyboard Focus Lost
222
/* Keyboard Focus Lost */
166
223
void Java_org_libsdl_app_SDLActivity_onNativeKeyboardFocusLost(
167
224
JNIEnv* env, jclass jcls)
180
237
Android_OnTouch(touch_device_id_in, pointer_finger_id_in, action, x, y, p);
184
241
void Java_org_libsdl_app_SDLActivity_onNativeAccel(
185
242
JNIEnv* env, jclass jcls,
186
243
jfloat x, jfloat y, jfloat z)
191
248
bHasNewData = true;
195
252
void Java_org_libsdl_app_SDLActivity_nativeLowMemory(
196
253
JNIEnv* env, jclass cls)
198
255
SDL_SendAppEvent(SDL_APP_LOWMEMORY);
202
259
void Java_org_libsdl_app_SDLActivity_nativeQuit(
203
260
JNIEnv* env, jclass cls)
205
// Inject a SDL_QUIT event
262
/* Inject a SDL_QUIT event */
207
264
SDL_SendAppEvent(SDL_APP_TERMINATING);
211
268
void Java_org_libsdl_app_SDLActivity_nativePause(
212
269
JNIEnv* env, jclass cls)
317
374
return s_active > 0;
321
SDL_bool Android_JNI_CreateContext(int majorVersion, int minorVersion,
322
int red, int green, int blue, int alpha,
323
int buffer, int depth, int stencil,
324
int buffers, int samples)
326
JNIEnv *env = Android_JNI_GetEnv();
330
EGL_GREEN_SIZE, green,
332
EGL_ALPHA_SIZE, alpha,
333
EGL_BUFFER_SIZE, buffer,
334
EGL_DEPTH_SIZE, depth,
335
EGL_STENCIL_SIZE, stencil,
336
EGL_SAMPLE_BUFFERS, buffers,
337
EGL_SAMPLES, samples,
338
EGL_RENDERABLE_TYPE, (majorVersion == 1 ? EGL_OPENGL_ES_BIT : EGL_OPENGL_ES2_BIT),
341
int len = SDL_arraysize(attribs);
345
array = (*env)->NewIntArray(env, len);
346
(*env)->SetIntArrayRegion(env, array, 0, len, attribs);
348
jboolean success = (*env)->CallStaticBooleanMethod(env, mActivityClass, midCreateGLContext, majorVersion, minorVersion, array);
350
(*env)->DeleteLocalRef(env, array);
352
return success ? SDL_TRUE : SDL_FALSE;
355
SDL_bool Android_JNI_DeleteContext(void)
357
/* There's only one context, so no parameter for now */
358
JNIEnv *env = Android_JNI_GetEnv();
359
(*env)->CallStaticVoidMethod(env, mActivityClass, midDeleteGLContext);
377
ANativeWindow* Android_JNI_GetNativeWindow(void)
381
JNIEnv *env = Android_JNI_GetEnv();
383
s = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetNativeSurface);
384
anw = ANativeWindow_fromSurface(env, s);
385
(*env)->DeleteLocalRef(env, s);
363
390
void Android_JNI_SwapWindow()
544
// Test for an exception and call SDL_SetError with its detail if one occurs
545
// If the parameter silent is truthy then SDL_SetError() will not be called.
571
/* Test for an exception and call SDL_SetError with its detail if one occurs */
572
/* If the parameter silent is truthy then SDL_SetError() will not be called. */
546
573
static bool Android_JNI_ExceptionOccurred(bool silent)
548
575
SDL_assert(LocalReferenceHolder_IsActive());
608
635
fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
609
636
ctx->hidden.androidio.position = 0;
611
// context = SDLActivity.getContext();
638
/* context = SDLActivity.getContext(); */
612
639
mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
613
640
"getContext","()Landroid/content/Context;");
614
641
context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
617
// assetManager = context.getAssets();
644
/* assetManager = context.getAssets(); */
618
645
mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
619
646
"getAssets", "()Landroid/content/res/AssetManager;");
620
647
assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
647
674
ctx->hidden.androidio.fd = (*mEnv)->GetIntField(mEnv, fd, descriptor);
648
675
ctx->hidden.androidio.assetFileDescriptorRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
650
// Seek to the correct offset in the file.
677
/* Seek to the correct offset in the file. */
651
678
lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
655
// Disabled log message because of spam on the Nexus 7
656
//__android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file");
682
/* Disabled log message because of spam on the Nexus 7 */
683
/* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
658
685
/* Try the old method using InputStream */
659
686
ctx->hidden.androidio.assetFileDescriptorRef = NULL;
661
// inputStream = assetManager.open(<filename>);
688
/* inputStream = assetManager.open(<filename>); */
662
689
mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager),
663
690
"open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
664
inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /*ACCESS_RANDOM*/);
691
inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
665
692
if (Android_JNI_ExceptionOccurred(false)) {
669
696
ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);
671
// Despite all the visible documentation on [Asset]InputStream claiming
672
// that the .available() method is not guaranteed to return the entire file
673
// size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
674
// android/apis/content/ReadAsset.java imply that Android's
675
// AssetInputStream.available() /will/ always return the total file size
677
// size = inputStream.available();
698
/* Despite all the visible documentation on [Asset]InputStream claiming
699
* that the .available() method is not guaranteed to return the entire file
700
* size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
701
* android/apis/content/ReadAsset.java imply that Android's
702
* AssetInputStream.available() /will/ always return the total file size
705
/* size = inputStream.available(); */
678
706
mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, inputStream),
679
707
"available", "()I");
680
708
ctx->hidden.androidio.size = (long)(*mEnv)->CallIntMethod(mEnv, inputStream, mid);
696
724
ctx->hidden.androidio.readableByteChannelRef =
697
725
(*mEnv)->NewGlobalRef(mEnv, readableByteChannel);
699
// Store .read id for reading purposes
727
/* Store .read id for reading purposes */
700
728
mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, readableByteChannel),
701
729
"read", "(Ljava/nio/ByteBuffer;)I");
702
730
ctx->hidden.androidio.readMethod = mid;
763
791
if (ctx->hidden.androidio.assetFileDescriptorRef) {
764
792
size_t bytesMax = size * maxnum;
765
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
793
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
766
794
bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
768
796
size_t result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
792
820
jobject byteBuffer = (*mEnv)->NewDirectByteBuffer(mEnv, buffer, bytesRemaining);
794
822
while (bytesRemaining > 0) {
795
// result = readableByteChannel.read(...);
823
/* result = readableByteChannel.read(...); */
796
824
int result = (*mEnv)->CallIntMethod(mEnv, readableByteChannel, readMethod, byteBuffer);
798
826
if (Android_JNI_ExceptionOccurred(false)) {
881
909
if (ctx->hidden.androidio.assetFileDescriptorRef) {
882
910
switch (whence) {
883
911
case RW_SEEK_SET:
884
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
912
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
885
913
offset += ctx->hidden.androidio.offset;
887
915
case RW_SEEK_CUR:
888
916
offset += ctx->hidden.androidio.position;
889
if (ctx->hidden.androidio.size != -1 /*UNKNOWN_LENGTH*/ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
917
if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
890
918
offset += ctx->hidden.androidio.offset;
892
920
case RW_SEEK_END:
947
975
} else if (movement < 0) {
948
// We can't seek backwards so we have to reopen the file and seek
949
// forwards which obviously isn't very efficient
976
/* We can't seek backwards so we have to reopen the file and seek */
977
/* forwards which obviously isn't very efficient */
950
978
Internal_Android_JNI_FileClose(ctx, false);
951
979
Internal_Android_JNI_FileOpen(ctx);
952
980
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
1065
// returns 0 on success or -1 on error (others undefined then)
1066
// returns truthy or falsy value in plugged, charged and battery
1067
// returns the value in seconds and percent or -1 if not available
1093
/* returns 0 on success or -1 on error (others undefined then)
1094
* returns truthy or falsy value in plugged, charged and battery
1095
* returns the value in seconds and percent or -1 if not available
1068
1097
int Android_JNI_GetPowerInfo(int* plugged, int* charged, int* battery, int* seconds, int* percent)
1070
1099
struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
1112
1141
(*env)->DeleteLocalRef(env, bname);
1115
GET_INT_EXTRA(plug, "plugged") // == BatteryManager.EXTRA_PLUGGED (API 5)
1144
GET_INT_EXTRA(plug, "plugged") /* == BatteryManager.EXTRA_PLUGGED (API 5) */
1116
1145
if (plug == -1) {
1117
1146
LocalReferenceHolder_Cleanup(&refs);
1120
// 1 == BatteryManager.BATTERY_PLUGGED_AC
1121
// 2 == BatteryManager.BATTERY_PLUGGED_USB
1149
/* 1 == BatteryManager.BATTERY_PLUGGED_AC */
1150
/* 2 == BatteryManager.BATTERY_PLUGGED_USB */
1122
1151
*plugged = (0 < plug) ? 1 : 0;
1126
GET_INT_EXTRA(status, "status") // == BatteryManager.EXTRA_STATUS (API 5)
1155
GET_INT_EXTRA(status, "status") /* == BatteryManager.EXTRA_STATUS (API 5) */
1127
1156
if (status == -1) {
1128
1157
LocalReferenceHolder_Cleanup(&refs);
1131
// 5 == BatteryManager.BATTERY_STATUS_FULL
1160
/* 5 == BatteryManager.BATTERY_STATUS_FULL */
1132
1161
*charged = (status == 5) ? 1 : 0;
1136
GET_BOOL_EXTRA(present, "present") // == BatteryManager.EXTRA_PRESENT (API 5)
1165
GET_BOOL_EXTRA(present, "present") /* == BatteryManager.EXTRA_PRESENT (API 5) */
1137
1166
*battery = present ? 1 : 0;
1141
*seconds = -1; // not possible
1170
*seconds = -1; /* not possible */
1145
GET_INT_EXTRA(level, "level") // == BatteryManager.EXTRA_LEVEL (API 5)
1146
GET_INT_EXTRA(scale, "scale") // == BatteryManager.EXTRA_SCALE (API 5)
1174
GET_INT_EXTRA(level, "level") /* == BatteryManager.EXTRA_LEVEL (API 5) */
1175
GET_INT_EXTRA(scale, "scale") /* == BatteryManager.EXTRA_SCALE (API 5) */
1147
1176
if ((level == -1) || (scale == -1)) {
1148
1177
LocalReferenceHolder_Cleanup(&refs);
1160
// sends message to be handled on the UI event dispatch thread
1189
/* returns number of found touch devices as return value and ids in parameter ids */
1190
int Android_JNI_GetTouchDeviceIds(int **ids) {
1191
JNIEnv *env = Android_JNI_GetEnv();
1192
jint sources = 4098; /* == InputDevice.SOURCE_TOUCHSCREEN */
1193
jmethodID mid = (*env)->GetStaticMethodID(env, mActivityClass, "inputGetInputDeviceIds", "(I)[I");
1194
jintArray array = (jintArray) (*env)->CallStaticObjectMethod(env, mActivityClass, mid, sources);
1198
number = (int) (*env)->GetArrayLength(env, array);
1200
jint* elements = (*env)->GetIntArrayElements(env, array, NULL);
1203
*ids = SDL_malloc(number * sizeof (**ids));
1204
for (i = 0; i < number; ++i) { /* not assuming sizeof (jint) == sizeof (int) */
1205
(*ids)[i] = elements[i];
1207
(*env)->ReleaseIntArrayElements(env, array, elements, JNI_ABORT);
1210
(*env)->DeleteLocalRef(env, array);
1215
/* sends message to be handled on the UI event dispatch thread */
1161
1216
int Android_JNI_SendMessage(int command, int param)
1163
1218
JNIEnv *env = Android_JNI_GetEnv();
1193
1248
void Android_JNI_HideTextInput()
1195
// has to match Activity constant
1250
/* has to match Activity constant */
1196
1251
const int COMMAND_TEXTEDIT_HIDE = 3;
1197
1252
Android_JNI_SendMessage(COMMAND_TEXTEDIT_HIDE, 0);
1200
1256
//////////////////////////////////////////////////////////////////////////////
1202
1258
// Functions exposed to SDL applications in SDL_system.h
1259
//////////////////////////////////////////////////////////////////////////////
1205
1262
void *SDL_AndroidGetJNIEnv()
1223
// return SDLActivity.getContext();
1280
/* return SDLActivity.getContext(); */
1224
1281
mid = (*env)->GetStaticMethodID(env, mActivityClass,
1225
1282
"getContext","()Landroid/content/Context;");
1226
1283
return (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1247
// context = SDLActivity.getContext();
1304
/* context = SDLActivity.getContext(); */
1248
1305
mid = (*env)->GetStaticMethodID(env, mActivityClass,
1249
1306
"getContext","()Landroid/content/Context;");
1250
1307
context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1252
// fileObj = context.getFilesDir();
1309
/* fileObj = context.getFilesDir(); */
1253
1310
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1254
1311
"getFilesDir", "()Ljava/io/File;");
1255
1312
fileObject = (*env)->CallObjectMethod(env, context, mid);
1262
// path = fileObject.getAbsolutePath();
1319
/* path = fileObject.getAbsolutePath(); */
1263
1320
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1264
1321
"getAbsolutePath", "()Ljava/lang/String;");
1265
1322
pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);
1296
1353
state = (*env)->GetStringUTFChars(env, stateString, NULL);
1298
// Print an info message so people debugging know the storage state
1355
/* Print an info message so people debugging know the storage state */
1299
1356
__android_log_print(ANDROID_LOG_INFO, "SDL", "external storage state: %s", state);
1301
1358
if (SDL_strcmp(state, "mounted") == 0) {
1333
// context = SDLActivity.getContext();
1390
/* context = SDLActivity.getContext(); */
1334
1391
mid = (*env)->GetStaticMethodID(env, mActivityClass,
1335
1392
"getContext","()Landroid/content/Context;");
1336
1393
context = (*env)->CallStaticObjectMethod(env, mActivityClass, mid);
1338
// fileObj = context.getExternalFilesDir();
1395
/* fileObj = context.getExternalFilesDir(); */
1339
1396
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
1340
1397
"getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;");
1341
1398
fileObject = (*env)->CallObjectMethod(env, context, mid, NULL);
1348
// path = fileObject.getAbsolutePath();
1405
/* path = fileObject.getAbsolutePath(); */
1349
1406
mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, fileObject),
1350
1407
"getAbsolutePath", "()Ljava/lang/String;");
1351
1408
pathString = (jstring)(*env)->CallObjectMethod(env, fileObject, mid);