Browse Source

Fixed bug 4297 - Android StrictMode policy. Remove APK expansion support

"In the second half of 2021, new apps will be required to publish with the Android App Bundle on Google Play"
(see https://developer.android.com/guide/app-bundle)
And "Android App Bundles don't support APK expansion (*.obb) files".
Sylvain Becker 4 years ago
parent
commit
965b466ee8

+ 0 - 70
android-project/app/src/main/java/org/libsdl/app/SDLActivity.java

@@ -1221,76 +1221,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
         }
     }
 
-    // APK expansion files support
-
-    /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
-    private static Object expansionFile;
-
-    /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
-    private static Method expansionFileMethod;
-
-    /**
-     * This method is called by SDL using JNI.
-     * @return an InputStream on success or null if no expansion file was used.
-     * @throws IOException on errors. Message is set for the SDL error message.
-     */
-    public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
-        // Get a ZipResourceFile representing a merger of both the main and patch files
-        if (expansionFile == null) {
-            String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
-            if (mainHint == null) {
-                return null; // no expansion use if no main version was set
-            }
-            String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
-            if (patchHint == null) {
-                return null; // no expansion use if no patch version was set
-            }
-
-            Integer mainVersion;
-            Integer patchVersion;
-            try {
-                mainVersion = Integer.valueOf(mainHint);
-                patchVersion = Integer.valueOf(patchHint);
-            } catch (NumberFormatException ex) {
-                ex.printStackTrace();
-                throw new IOException("No valid file versions set for APK expansion files", ex);
-            }
-
-            try {
-                // To avoid direct dependency on Google APK expansion 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, SDL.getContext(), mainVersion, patchVersion);
-
-                expansionFileMethod = expansionFile.getClass()
-                    .getMethod("getInputStream", String.class);
-            } catch (Exception ex) {
-                ex.printStackTrace();
-                expansionFile = null;
-                expansionFileMethod = null;
-                throw new IOException("Could not access APK expansion support library", ex);
-            }
-        }
-
-        // 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) {
-            // calling "getInputStream" failed
-            ex.printStackTrace();
-            throw new IOException("Could not open stream from APK expansion file", ex);
-        }
-
-        if (fileStream == null) {
-            // calling "getInputStream" was successful but null was returned
-            throw new IOException("Could not find path in APK expansion file");
-        }
-
-        return fileStream;
-    }
-
     // Messagebox
 
     /** Result of current messagebox. Also used for blocking the calling thread. */

+ 1 - 9
include/SDL_rwops.h

@@ -96,15 +96,7 @@ typedef struct SDL_RWops
 #if defined(__ANDROID__)
         struct
         {
-            void *fileNameRef;
-            void *inputStreamRef;
-            void *readableByteChannelRef;
-            void *readMethod;
-            void *assetFileDescriptorRef;
-            long position;
-            long size;
-            long offset;
-            int fd;
+            void *asset;
         } androidio;
 #elif defined(__WIN32__)
         struct

+ 32 - 358
src/core/android/SDL_android.c

@@ -311,7 +311,6 @@ static jmethodID midIsScreenKeyboardShown;
 static jmethodID midIsTablet;
 static jmethodID midManualBackButton;
 static jmethodID midMinimizeWindow;
-static jmethodID midOpenAPKExpansionInputStream;
 static jmethodID midRequestPermission;
 static jmethodID midSendMessage;
 static jmethodID midSetActivityTitle;
@@ -590,7 +589,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
     midIsTablet = (*env)->GetStaticMethodID(env, mActivityClass, "isTablet", "()Z");
     midManualBackButton = (*env)->GetStaticMethodID(env, mActivityClass, "manualBackButton", "()V");
     midMinimizeWindow = (*env)->GetStaticMethodID(env, mActivityClass, "minimizeWindow","()V");
-    midOpenAPKExpansionInputStream = (*env)->GetStaticMethodID(env, mActivityClass, "openAPKExpansionInputStream", "(Ljava/lang/String;)Ljava/io/InputStream;");
     midRequestPermission = (*env)->GetStaticMethodID(env, mActivityClass, "requestPermission", "(Ljava/lang/String;I)V");
     midSendMessage = (*env)->GetStaticMethodID(env, mActivityClass, "sendMessage", "(II)Z");
     midSetActivityTitle = (*env)->GetStaticMethodID(env, mActivityClass, "setActivityTitle","(Ljava/lang/String;)Z");
@@ -620,7 +618,6 @@ JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetupJNI)(JNIEnv *env, jclass cl
         !midIsTablet ||
         !midManualBackButton ||
         !midMinimizeWindow ||
-        !midOpenAPKExpansionInputStream ||
         !midRequestPermission ||
         !midSendMessage ||
         !midSetActivityTitle ||
@@ -1867,245 +1864,43 @@ static void Internal_Android_Destroy_AssetManager() {
     }
 }
 
-static int Internal_Android_JNI_FileOpen(SDL_RWops *ctx)
-{
-    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
-
-    int result = 0;
-
-    jmethodID mid;
-    jobject context;
-    jobject assetManager;
-    jobject inputStream;
-    jclass channels;
-    jobject readableByteChannel;
-    jstring fileNameJString;
-    jobject fd;
-    jclass fdCls;
-    jfieldID descriptor;
-
-    JNIEnv *env = Android_JNI_GetEnv();
-    if (!LocalReferenceHolder_Init(&refs, env)) {
-        goto failure;
-    }
-
-    fileNameJString = (jstring)ctx->hidden.androidio.fileNameRef;
-    ctx->hidden.androidio.position = 0;
-
-    /* context = SDLActivity.getContext(); */
-    context = (*env)->CallStaticObjectMethod(env, mActivityClass, midGetContext);
-
-    /* assetManager = context.getAssets(); */
-    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, context),
-            "getAssets", "()Landroid/content/res/AssetManager;");
-    assetManager = (*env)->CallObjectMethod(env, context, mid);
-
-    /* First let's try opening the file to obtain an AssetFileDescriptor.
-    * This method reads the files directly from the APKs using standard *nix calls
-    */
-    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, assetManager), "openFd", "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;");
-    inputStream = (*env)->CallObjectMethod(env, assetManager, mid, fileNameJString);
-    if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
-        goto fallback;
-    }
-
-    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getStartOffset", "()J");
-    ctx->hidden.androidio.offset = (long)(*env)->CallLongMethod(env, inputStream, mid);
-    if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
-        goto fallback;
-    }
-
-    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getDeclaredLength", "()J");
-    ctx->hidden.androidio.size = (long)(*env)->CallLongMethod(env, inputStream, mid);
-    if (Android_JNI_ExceptionOccurred(SDL_TRUE)) {
-        goto fallback;
-    }
-
-    mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream), "getFileDescriptor", "()Ljava/io/FileDescriptor;");
-    fd = (*env)->CallObjectMethod(env, inputStream, mid);
-    fdCls = (*env)->GetObjectClass(env, fd);
-    descriptor = (*env)->GetFieldID(env, fdCls, "descriptor", "I");
-    ctx->hidden.androidio.fd = (*env)->GetIntField(env, fd, descriptor);
-    ctx->hidden.androidio.assetFileDescriptorRef = (*env)->NewGlobalRef(env, inputStream);
-
-    /* Seek to the correct offset in the file. */
-    lseek(ctx->hidden.androidio.fd, (off_t)ctx->hidden.androidio.offset, SEEK_SET);
-
-    if (0) {
-fallback:
-        /* Disabled log message because of spam on the Nexus 7 */
-        /* __android_log_print(ANDROID_LOG_DEBUG, "SDL", "Falling back to legacy InputStream method for opening file"); */
-
-        /* Try the old method using InputStream */
-        ctx->hidden.androidio.assetFileDescriptorRef = NULL;
-
-        /* inputStream = assetManager.open(<filename>); */
-        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, assetManager),
-                "open", "(Ljava/lang/String;I)Ljava/io/InputStream;");
-        inputStream = (*env)->CallObjectMethod(env, assetManager, mid, fileNameJString, 1 /* ACCESS_RANDOM */);
-        if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-            /* Try fallback to APK expansion files */
-            inputStream = (*env)->CallStaticObjectMethod(env, mActivityClass, midOpenAPKExpansionInputStream, fileNameJString);
-
-            /* Exception is checked first because it always needs to be cleared.
-             * If no exception occurred then the last SDL error message is kept.
-             */
-            if (Android_JNI_ExceptionOccurred(SDL_FALSE) || !inputStream) {
-                goto failure;
-            }
-        }
-
-        ctx->hidden.androidio.inputStreamRef = (*env)->NewGlobalRef(env, inputStream);
-
-        /* 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(); */
-        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
-                "available", "()I");
-        ctx->hidden.androidio.size = (long)(*env)->CallIntMethod(env, inputStream, mid);
-        if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-            goto failure;
-        }
-
-        /* readableByteChannel = Channels.newChannel(inputStream); */
-        channels = (*env)->FindClass(env, "java/nio/channels/Channels");
-        mid = (*env)->GetStaticMethodID(env, channels,
-                "newChannel",
-                "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;");
-        readableByteChannel = (*env)->CallStaticObjectMethod(
-                env, channels, mid, inputStream);
-        if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-            goto failure;
-        }
-
-        ctx->hidden.androidio.readableByteChannelRef =
-            (*env)->NewGlobalRef(env, readableByteChannel);
-
-        /* Store .read id for reading purposes */
-        mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, readableByteChannel),
-                "read", "(Ljava/nio/ByteBuffer;)I");
-        ctx->hidden.androidio.readMethod = mid;
-    }
-
-    if (0) {
-failure:
-        result = -1;
-
-        (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.fileNameRef);
-
-        if(ctx->hidden.androidio.inputStreamRef != NULL) {
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.inputStreamRef);
-        }
-
-        if(ctx->hidden.androidio.readableByteChannelRef != NULL) {
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.readableByteChannelRef);
-        }
-
-        if(ctx->hidden.androidio.assetFileDescriptorRef != NULL) {
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
-        }
-
-    }
-
-    LocalReferenceHolder_Cleanup(&refs);
-    return result;
-}
-
 int Android_JNI_FileOpen(SDL_RWops *ctx,
         const char *fileName, const char *mode)
 {
-    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
-    JNIEnv *env = Android_JNI_GetEnv();
-    int retval;
-    jstring fileNameJString;
+    AAsset *asset = NULL;
+    ctx->hidden.androidio.asset = NULL;
 
-    if (!LocalReferenceHolder_Init(&refs, env)) {
-        LocalReferenceHolder_Cleanup(&refs);
+    if (asset_manager == NULL) {
+        Internal_Android_Create_AssetManager();
+    }
+
+    if (asset_manager == NULL) {
         return -1;
     }
 
-    if (!ctx) {
-        LocalReferenceHolder_Cleanup(&refs);
+    asset = AAssetManager_open(asset_manager, fileName, AASSET_MODE_UNKNOWN);
+    if (asset == NULL) {
         return -1;
     }
 
-    fileNameJString = (*env)->NewStringUTF(env, fileName);
-    ctx->hidden.androidio.fileNameRef = (*env)->NewGlobalRef(env, fileNameJString);
-    ctx->hidden.androidio.inputStreamRef = NULL;
-    ctx->hidden.androidio.readableByteChannelRef = NULL;
-    ctx->hidden.androidio.readMethod = NULL;
-    ctx->hidden.androidio.assetFileDescriptorRef = NULL;
 
-    retval = Internal_Android_JNI_FileOpen(ctx);
-    LocalReferenceHolder_Cleanup(&refs);
-    return retval;
+    ctx->hidden.androidio.asset = (void*) asset;
+    return 0;
 }
 
-size_t Android_JNI_FileRead(SDL_RWops *ctx, void *buffer,
+size_t Android_JNI_FileRead(SDL_RWops* ctx, void* buffer,
         size_t size, size_t maxnum)
 {
-    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
+    size_t result;
+    AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
+    result = AAsset_read(asset, buffer, size * maxnum);
 
-    if (ctx->hidden.androidio.assetFileDescriptorRef) {
-        size_t bytesMax = size * maxnum;
-        size_t result;
-        if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && ctx->hidden.androidio.position + bytesMax > ctx->hidden.androidio.size) {
-            bytesMax = ctx->hidden.androidio.size - ctx->hidden.androidio.position;
-        }
-        result = read(ctx->hidden.androidio.fd, buffer, bytesMax );
-        if (result > 0) {
-            ctx->hidden.androidio.position += result;
-            LocalReferenceHolder_Cleanup(&refs);
-            return result / size;
-        }
-        LocalReferenceHolder_Cleanup(&refs);
-        return 0;
+    if (result > 0) {
+        /* Number of chuncks */
+        return (result / size);
     } else {
-        jlong bytesRemaining = (jlong) (size * maxnum);
-        jlong bytesMax = (jlong) (ctx->hidden.androidio.size -  ctx->hidden.androidio.position);
-        int bytesRead = 0;
-        JNIEnv *env;
-        jobject readableByteChannel;
-        jmethodID readMethod;
-        jobject byteBuffer;
-
-        /* Don't read more bytes than those that remain in the file, otherwise we get an exception */
-        if (bytesRemaining >  bytesMax) bytesRemaining = bytesMax;
-
-        env = Android_JNI_GetEnv();
-        if (!LocalReferenceHolder_Init(&refs, env)) {
-            LocalReferenceHolder_Cleanup(&refs);
-            return 0;
-        }
-
-        readableByteChannel = (jobject)ctx->hidden.androidio.readableByteChannelRef;
-        readMethod = (jmethodID)ctx->hidden.androidio.readMethod;
-        byteBuffer = (*env)->NewDirectByteBuffer(env, buffer, bytesRemaining);
-
-        while (bytesRemaining > 0) {
-            /* result = readableByteChannel.read(...); */
-            int result = (*env)->CallIntMethod(env, readableByteChannel, readMethod, byteBuffer);
-
-            if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-                LocalReferenceHolder_Cleanup(&refs);
-                return 0;
-            }
-
-            if (result < 0) {
-                break;
-            }
-
-            bytesRemaining -= result;
-            bytesRead += result;
-            ctx->hidden.androidio.position += result;
-        }
-        LocalReferenceHolder_Cleanup(&refs);
-        return bytesRead / size;
+        /* Error or EOF */
+        return result;
     }
 }
 
@@ -2116,148 +1911,27 @@ size_t Android_JNI_FileWrite(SDL_RWops *ctx, const void *buffer,
     return 0;
 }
 
-static int Internal_Android_JNI_FileClose(SDL_RWops *ctx, SDL_bool release)
+Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
 {
-    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
-
-    int result = 0;
-    JNIEnv *env = Android_JNI_GetEnv();
-
-    if (!LocalReferenceHolder_Init(&refs, env)) {
-        LocalReferenceHolder_Cleanup(&refs);
-        return SDL_SetError("Failed to allocate enough JVM local references");
-    }
-
-    if (ctx) {
-        if (release) {
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.fileNameRef);
-        }
-
-        if (ctx->hidden.androidio.assetFileDescriptorRef) {
-            jobject inputStream = (jobject)ctx->hidden.androidio.assetFileDescriptorRef;
-            jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
-                    "close", "()V");
-            (*env)->CallVoidMethod(env, inputStream, mid);
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.assetFileDescriptorRef);
-            if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-                result = -1;
-            }
-        }
-        else {
-            jobject inputStream = (jobject)ctx->hidden.androidio.inputStreamRef;
-
-            /* inputStream.close(); */
-            jmethodID mid = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, inputStream),
-                    "close", "()V");
-            (*env)->CallVoidMethod(env, inputStream, mid);
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.inputStreamRef);
-            (*env)->DeleteGlobalRef(env, (jobject)ctx->hidden.androidio.readableByteChannelRef);
-            if (Android_JNI_ExceptionOccurred(SDL_FALSE)) {
-                result = -1;
-            }
-        }
-
-        if (release) {
-            SDL_FreeRW(ctx);
-        }
-    }
-
-    LocalReferenceHolder_Cleanup(&refs);
+    off64_t result;
+    AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
+    result = AAsset_getLength64(asset);
     return result;
 }
 
-
-Sint64 Android_JNI_FileSize(SDL_RWops *ctx)
+Sint64 Android_JNI_FileSeek(SDL_RWops* ctx, Sint64 offset, int whence)
 {
-    return ctx->hidden.androidio.size;
-}
-
-Sint64 Android_JNI_FileSeek(SDL_RWops *ctx, Sint64 offset, int whence)
-{
-    if (ctx->hidden.androidio.assetFileDescriptorRef) {
-        off_t ret;
-        switch (whence) {
-            case RW_SEEK_SET:
-                if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
-                offset += ctx->hidden.androidio.offset;
-                break;
-            case RW_SEEK_CUR:
-                offset += ctx->hidden.androidio.position;
-                if (ctx->hidden.androidio.size != -1 /* UNKNOWN_LENGTH */ && offset > ctx->hidden.androidio.size) offset = ctx->hidden.androidio.size;
-                offset += ctx->hidden.androidio.offset;
-                break;
-            case RW_SEEK_END:
-                offset = ctx->hidden.androidio.offset + ctx->hidden.androidio.size + offset;
-                break;
-            default:
-                return SDL_SetError("Unknown value for 'whence'");
-        }
-
-        ret = lseek(ctx->hidden.androidio.fd, (off_t)offset, SEEK_SET);
-        if (ret == -1) return -1;
-        ctx->hidden.androidio.position = ret - ctx->hidden.androidio.offset;
-    } else {
-        Sint64 newPosition;
-        Sint64 movement;
-
-        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:
-                return SDL_SetError("Unknown value for 'whence'");
-        }
-
-        /* Validate the new position */
-        if (newPosition < 0) {
-            return SDL_Error(SDL_EFSEEK);
-        }
-        if (newPosition > ctx->hidden.androidio.size) {
-            newPosition = ctx->hidden.androidio.size;
-        }
-
-        movement = newPosition - ctx->hidden.androidio.position;
-        if (movement > 0) {
-            unsigned char buffer[4096];
-
-            /* The easy case where we're seeking forwards */
-            while (movement > 0) {
-                Sint64 amount = sizeof (buffer);
-                size_t result;
-                if (amount > movement) {
-                    amount = movement;
-                }
-                result = Android_JNI_FileRead(ctx, buffer, 1, (size_t)amount);
-                if (result <= 0) {
-                    /* Failed to read/skip the required amount, so fail */
-                    return -1;
-                }
-
-                movement -= result;
-            }
-
-        } 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 */
-            Internal_Android_JNI_FileClose(ctx, SDL_FALSE);
-            Internal_Android_JNI_FileOpen(ctx);
-            Android_JNI_FileSeek(ctx, newPosition, RW_SEEK_SET);
-        }
-    }
-
-    return ctx->hidden.androidio.position;
-
+    off64_t result;
+    AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
+    result = AAsset_seek64(asset, offset, whence);
+    return result;
 }
 
 int Android_JNI_FileClose(SDL_RWops *ctx)
 {
-    return Internal_Android_JNI_FileClose(ctx, SDL_TRUE);
+    AAsset *asset = (AAsset*) ctx->hidden.androidio.asset;
+    AAsset_close(asset);
+    return 0;
 }
 
 int Android_JNI_SetClipboardText(const char *text)