Merged changes from Alexey Petruchik to support Android obb files
authorSam Lantinga <slouken@libsdl.org>
Sat, 21 Jun 2014 20:35:36 -0700
changeset 8900 537445b25e4e
parent 8895 070b7f655147 (current diff)
parent 8899 7f19291e720c (diff)
child 8902 5192470456ae
Merged changes from Alexey Petruchik to support Android obb files http://developer.android.com/google/play/expansion-files.html
android-project/src/org/libsdl/app/SDLActivity.java
include/SDL_hints.h
src/core/android/SDL_android.c
--- a/android-project/src/org/libsdl/app/SDLActivity.java	Sat Jun 21 17:31:36 2014 -0700
+++ b/android-project/src/org/libsdl/app/SDLActivity.java	Sat Jun 21 20:35:36 2014 -0700
@@ -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.media.*;
 import android.hardware.*;
 
-
 /**
     SDL Activity
 */
@@ -296,6 +298,7 @@
                                                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 @@
             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;
+    }
 }
 
 /**
--- a/include/SDL_hints.h	Sat Jun 21 17:31:36 2014 -0700
+++ b/include/SDL_hints.h	Sat Jun 21 20:35:36 2014 -0700
@@ -457,6 +457,16 @@
  */
 #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
--- a/src/core/android/SDL_android.c	Sat Jun 21 17:31:36 2014 -0700
+++ b/src/core/android/SDL_android.c	Sat Jun 21 20:35:36 2014 -0700
@@ -385,7 +385,15 @@
     (*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 @@
                 "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);