Skip to content
This repository has been archived by the owner on Feb 11, 2021. It is now read-only.

Commit

Permalink
Allow Android platforms to read from .apk files via the RWOPS interface.
Browse files Browse the repository at this point in the history
Fixes Bugzilla #1261.

Thanks to Tim Angus for the patch!
  • Loading branch information
icculus committed Jul 29, 2011
1 parent 3fa7b7d commit 9cd6dd4
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 2 deletions.
4 changes: 4 additions & 0 deletions android-project/src/org/libsdl/app/SDLActivity.java
Expand Up @@ -114,6 +114,10 @@ public static void setActivityTitle(String title) {
mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
}

public static Context getContext() {
return mSingleton;
}

// Audio
private static Object buf;

Expand Down
17 changes: 16 additions & 1 deletion include/SDL_rwops.h
Expand Up @@ -82,7 +82,21 @@ typedef struct SDL_RWops
Uint32 type;
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
{
SDL_bool append;
Expand All @@ -95,6 +109,7 @@ typedef struct SDL_RWops
} buffer;
} windowsio;
#endif

#ifdef HAVE_STDIO_H
struct
{
Expand Down
230 changes: 230 additions & 0 deletions src/core/android/SDL_android.cpp
Expand Up @@ -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: */
8 changes: 8 additions & 0 deletions src/core/android/SDL_android.h
Expand Up @@ -39,6 +39,14 @@ extern void* Android_JNI_GetAudioBuffer();
extern void Android_JNI_WriteAudioBuffer();
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++ */
#ifdef __cplusplus
/* *INDENT-OFF* */
Expand Down
19 changes: 18 additions & 1 deletion src/file/SDL_rwops.c
Expand Up @@ -31,6 +31,10 @@
#include "cocoa/SDL_rwopsbundlesupport.h"
#endif /* __APPLE__ */

#ifdef ANDROID
#include "../core/android/SDL_android.h"
#endif

#ifdef __NDS__
/* include libfat headers for fatInitDefault(). */
#include <fat.h>
Expand Down Expand Up @@ -441,7 +445,20 @@ SDL_RWFromFile(const char *file, const char *mode)
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
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();
if (!rwops)
return NULL; /* SDL_SetError already setup by SDL_AllocRW() */
Expand Down

0 comments on commit 9cd6dd4

Please sign in to comment.