diff --git a/android-project/src/org/libsdl/app/SDLActivity.java b/android-project/src/org/libsdl/app/SDLActivity.java index d7598046d..59d6a694d 100644 --- a/android-project/src/org/libsdl/app/SDLActivity.java +++ b/android-project/src/org/libsdl/app/SDLActivity.java @@ -1,10 +1,13 @@ package org.libsdl.app; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.lang.reflect.Method; import android.app.*; import android.content.*; @@ -20,7 +23,6 @@ import android.graphics.*; import android.media.*; import android.hardware.*; - /** SDL Activity */ @@ -296,6 +298,7 @@ public class SDLActivity extends Activity { int is_accelerometer, int nbuttons, int naxes, int nhats, int nballs); public static native int nativeRemoveJoystick(int device_id); + public static native String nativeGetHint(String name); /** * This method is called by SDL using JNI. @@ -531,7 +534,52 @@ public class SDLActivity extends Activity { mJoystickHandler.pollInputDevices(); } } - + + // APK extension files support + + /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ + private Object expansionFile; + + /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ + private Method expansionFileMethod; + + public InputStream openAPKExtensionInputStream(String fileName) throws IOException { + // Get a ZipResourceFile representing a merger of both the main and patch files + if (expansionFile == null) { + Integer mainVersion = Integer.parseInt(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION")); + Integer patchVersion = Integer.parseInt(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION")); + + try { + // To avoid direct dependency on Google APK extension library that is + // not a part of Android SDK we access it using reflection + expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") + .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) + .invoke(null, this, mainVersion, patchVersion); + + expansionFileMethod = expansionFile.getClass() + .getMethod("getInputStream", String.class); + } catch (Exception ex) { + ex.printStackTrace(); + expansionFile = null; + expansionFileMethod = null; + } + } + + // Get an input stream for a known file inside the expansion file ZIPs + InputStream fileStream; + try { + fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); + } catch (Exception ex) { + ex.printStackTrace(); + fileStream = null; + } + + if (fileStream == null) { + throw new IOException(); + } + + return fileStream; + } } /** diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 249f24d1f..29d2b059e 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -457,6 +457,16 @@ extern "C" { */ #define SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES "SDL_VIDEO_MAC_FULLSCREEN_SPACES" +/** + * \brief Android APK expansion main file version. Should be a string number like "1", "2" etc. + */ +#define SDL_HINT_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION "SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION" + +/** + * \brief Android APK expansion patch file version. Should be a string number like "1", "2" etc. + */ +#define SDL_HINT_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION "SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION" + /** * \brief An enumeration of hint priorities diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index 3b18d17a0..096770dc8 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -385,7 +385,15 @@ JNIEXPORT void JNICALL Java_org_libsdl_app_SDLInputConnection_nativeSetComposing (*env)->ReleaseStringUTFChars(env, text, utftext); } +jstring Java_org_libsdl_app_SDLActivity_nativeGetHint(JNIEnv* env, jclass cls, jstring name) { + const char *utfname = (*env)->GetStringUTFChars(env, name, NULL); + const char *hint = SDL_GetHint(utfname); + jstring result = (*env)->NewStringUTF(env, hint); + (*env)->ReleaseStringUTFChars(env, name, utfname); + + return result; +} /******************************************************************************* Functions called by SDL into Java @@ -758,7 +766,14 @@ fallback: "open", "(Ljava/lang/String;I)Ljava/io/InputStream;"); inputStream = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */); if (Android_JNI_ExceptionOccurred(false)) { - goto failure; + // Try fallback to APK Extension files + mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), + "openAPKExtensionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;"); + inputStream = (*mEnv)->CallObjectMethod(mEnv, context, mid, fileNameJString); + + if (Android_JNI_ExceptionOccurred(false)) { + goto failure; + } } ctx->hidden.androidio.inputStreamRef = (*mEnv)->NewGlobalRef(mEnv, inputStream);