Commit 247346d5 authored by Ryan C. Gordon's avatar Ryan C. Gordon

Allow Android platforms to read from .apk files via the RWOPS interface.

Fixes Bugzilla #1261.

Thanks to Tim Angus for the patch!
parent 58faae48
...@@ -114,6 +114,10 @@ public class SDLActivity extends Activity { ...@@ -114,6 +114,10 @@ public class SDLActivity extends Activity {
mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
} }
public static Context getContext() {
return mSingleton;
}
// Audio // Audio
private static Object buf; private static Object buf;
......
...@@ -82,7 +82,21 @@ typedef struct SDL_RWops ...@@ -82,7 +82,21 @@ typedef struct SDL_RWops
Uint32 type; Uint32 type;
union union
{ {
#ifdef __WIN32__ #if defined(ANDROID)
struct
{
void *fileName;
void *fileNameRef;
void *inputStream;
void *inputStreamRef;
void *skipMethod;
void *readableByteChannel;
void *readableByteChannelRef;
void *readMethod;
long position;
int size;
} androidio;
#elif defined(__WIN32__)
struct struct
{ {
SDL_bool append; SDL_bool append;
...@@ -95,6 +109,7 @@ typedef struct SDL_RWops ...@@ -95,6 +109,7 @@ typedef struct SDL_RWops
} buffer; } buffer;
} windowsio; } windowsio;
#endif #endif
#ifdef HAVE_STDIO_H #ifdef HAVE_STDIO_H
struct struct
{ {
......
...@@ -259,4 +259,234 @@ extern "C" void Android_JNI_CloseAudioDevice() ...@@ -259,4 +259,234 @@ extern "C" void Android_JNI_CloseAudioDevice()
} }
} }
static int Android_JNI_FileOpen(SDL_RWops* ctx)
{
jstring fileNameJString = (jstring)ctx->hidden.androidio.fileName;
// context = SDLActivity.getContext();
jmethodID mid = mEnv->GetStaticMethodID(mActivityClass,
"getContext","()Landroid/content/Context;");
jobject context = mEnv->CallStaticObjectMethod(mActivityClass, mid);
// assetManager = context.getAssets();
mid = mEnv->GetMethodID(mEnv->GetObjectClass(context),
"getAssets","()Landroid/content/res/AssetManager;");
jobject assetManager = mEnv->CallObjectMethod(context, mid);
// inputStream = assetManager.open(<filename>);
mEnv->ExceptionClear();
mid = mEnv->GetMethodID(mEnv->GetObjectClass(assetManager),
"open", "(Ljava/lang/String;)Ljava/io/InputStream;");
jobject inputStream = mEnv->CallObjectMethod(assetManager, mid, fileNameJString);
if (mEnv->ExceptionOccurred()) {
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
return -1;
} else {
ctx->hidden.androidio.inputStream = inputStream;
ctx->hidden.androidio.inputStreamRef = mEnv->NewGlobalRef(inputStream);
}
// Store .skip id for seeking purposes
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
"skip", "(J)J");
ctx->hidden.androidio.skipMethod = mid;
// Despite all the visible documentation on [Asset]InputStream claiming
// that the .available() method is not guaranteed to return the entire file
// size, comments in <sdk>/samples/<ver>/ApiDemos/src/com/example/ ...
// android/apis/content/ReadAsset.java imply that Android's
// AssetInputStream.available() /will/ always return the total file size
// size = inputStream.available();
mEnv->ExceptionClear();
mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
"available", "()I");
ctx->hidden.androidio.size = mEnv->CallIntMethod(inputStream, mid);
if (mEnv->ExceptionOccurred()) {
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
return -1;
}
// readableByteChannel = Channels.newChannel(inputStream);
mEnv->ExceptionClear();
jclass channels = mEnv->FindClass("java/nio/channels/Channels");
mid = mEnv->GetStaticMethodID(channels,
"newChannel",
"(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
jobject readableByteChannel = mEnv->CallStaticObjectMethod(
channels, mid, inputStream);
if (mEnv->ExceptionOccurred()) {
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
return -1;
} else {
ctx->hidden.androidio.readableByteChannel = readableByteChannel;
ctx->hidden.androidio.readableByteChannelRef =
mEnv->NewGlobalRef(readableByteChannel);
}
// Store .read id for reading purposes
mid = mEnv->GetMethodID(mEnv->GetObjectClass(readableByteChannel),
"read", "(Ljava/nio/ByteBuffer;)I");
ctx->hidden.androidio.readMethod = mid;
ctx->hidden.androidio.position = 0;
return 0;
}
extern "C" int Android_JNI_FileOpen(SDL_RWops* ctx,
const char* fileName, const char*)
{
if (!ctx) {
return -1;
}
jstring fileNameJString = mEnv->NewStringUTF(fileName);
ctx->hidden.androidio.fileName = fileNameJString;
ctx->hidden.androidio.fileNameRef = mEnv->NewGlobalRef(fileNameJString);
return Android_JNI_FileOpen(ctx);
}
extern "C" size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
size_t size, size_t maxnum)
{
int bytesRemaining = size * maxnum;
int bytesRead = 0;
jobject readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannel;
jmethodID readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
jobject byteBuffer = mEnv->NewDirectByteBuffer(buffer, bytesRemaining);
mEnv->ExceptionClear();
while (bytesRemaining > 0) {
// result = readableByteChannel.read(...);
int result = mEnv->CallIntMethod(readableByteChannel, readMethod, byteBuffer);
if (mEnv->ExceptionOccurred()) {
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
return 0;
}
if (result < 0) {
break;
}
bytesRemaining -= result;
bytesRead += result;
ctx->hidden.androidio.position += result;
}
return bytesRead / size;
}
extern "C" size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer,
size_t size, size_t num)
{
SDL_SetError("Cannot write to Android package filesystem");
return 0;
}
static int Android_JNI_FileClose(SDL_RWops* ctx, bool release)
{
int result = 0;
if (ctx) {
if (release) {
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.fileNameRef);
}
jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
// inputStream.close();
mEnv->ExceptionClear();
jmethodID mid = mEnv->GetMethodID(mEnv->GetObjectClass(inputStream),
"close", "()V");
mEnv->CallVoidMethod(inputStream, mid);
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.inputStreamRef);
mEnv->DeleteGlobalRef((jobject)ctx->hidden.androidio.readableByteChannelRef);
if (mEnv->ExceptionOccurred()) {
result = -1;
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
}
if (release) {
SDL_FreeRW(ctx);
}
}
return result;
}
extern "C" long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence)
{
long newPosition;
switch (whence) {
case RW_SEEK_SET:
newPosition = offset;
break;
case RW_SEEK_CUR:
newPosition = ctx->hidden.androidio.position + offset;
break;
case RW_SEEK_END:
newPosition = ctx->hidden.androidio.size + offset;
break;
default:
SDL_SetError("Unknown value for 'whence'");
return -1;
}
if (newPosition < 0) {
newPosition = 0;
}
if (newPosition > ctx->hidden.androidio.size) {
newPosition = ctx->hidden.androidio.size;
}
long movement = newPosition - ctx->hidden.androidio.position;
jobject inputStream = (jobject)ctx->hidden.androidio.inputStream;
jmethodID skipMethod = (jmethodID)ctx->hidden.androidio.skipMethod;
if (movement > 0) {
// The easy case where we're seeking forwards
mEnv->ExceptionClear();
while (movement > 0) {
// inputStream.skip(...);
movement -= mEnv->CallLongMethod(inputStream, skipMethod, movement);
if (mEnv->ExceptionOccurred()) {
mEnv->ExceptionDescribe();
mEnv->ExceptionClear();
SDL_SetError("Exception while seeking");
return -1;
}
}
} else if (movement < 0) {
// We can't seek backwards so we have to reopen the file and seek
// forwards which obviously isn't very efficient
Android_JNI_FileClose(ctx, false);
Android_JNI_FileOpen(ctx);
Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
}
ctx->hidden.androidio.position = newPosition;
return ctx->hidden.androidio.position;
}
extern "C" int Android_JNI_FileClose(SDL_RWops* ctx)
{
return Android_JNI_FileClose(ctx, true);
}
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */
...@@ -39,6 +39,14 @@ extern void* Android_JNI_GetAudioBuffer(); ...@@ -39,6 +39,14 @@ extern void* Android_JNI_GetAudioBuffer();
extern void Android_JNI_WriteAudioBuffer(); extern void Android_JNI_WriteAudioBuffer();
extern void Android_JNI_CloseAudioDevice(); extern void Android_JNI_CloseAudioDevice();
#include "SDL_rwops.h"
int Android_JNI_FileOpen(SDL_RWops* ctx, const char* fileName, const char* mode);
long Android_JNI_FileSeek(SDL_RWops* ctx, long offset, int whence);
size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer, size_t size, size_t maxnum);
size_t Android_JNI_FileWrite(SDL_RWops* ctx, const void* buffer, size_t size, size_t num);
int Android_JNI_FileClose(SDL_RWops* ctx);
/* Ends C function definitions when using C++ */ /* Ends C function definitions when using C++ */
#ifdef __cplusplus #ifdef __cplusplus
/* *INDENT-OFF* */ /* *INDENT-OFF* */
......
...@@ -31,6 +31,10 @@ ...@@ -31,6 +31,10 @@
#include "cocoa/SDL_rwopsbundlesupport.h" #include "cocoa/SDL_rwopsbundlesupport.h"
#endif /* __APPLE__ */ #endif /* __APPLE__ */
#ifdef ANDROID
#include "../core/android/SDL_android.h"
#endif
#ifdef __NDS__ #ifdef __NDS__
/* include libfat headers for fatInitDefault(). */ /* include libfat headers for fatInitDefault(). */
#include <fat.h> #include <fat.h>
...@@ -441,7 +445,20 @@ SDL_RWFromFile(const char *file, const char *mode) ...@@ -441,7 +445,20 @@ SDL_RWFromFile(const char *file, const char *mode)
SDL_SetError("SDL_RWFromFile(): No file or no mode specified"); SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
return NULL; return NULL;
} }
#if defined(__WIN32__) #if defined(ANDROID)
rwops = SDL_AllocRW();
if (!rwops)
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
if (Android_JNI_FileOpen(rwops, file, mode) < 0) {
SDL_FreeRW(rwops);
return NULL;
}
rwops->seek = Android_JNI_FileSeek;
rwops->read = Android_JNI_FileRead;
rwops->write = Android_JNI_FileWrite;
rwops->close = Android_JNI_FileClose;
#elif defined(__WIN32__)
rwops = SDL_AllocRW(); rwops = SDL_AllocRW();
if (!rwops) if (!rwops)
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */ return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment