From 12e9e9788a397e25bcee9b55c23aa72c77634b61 Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sat, 20 May 2023 18:53:20 +0200 Subject: [PATCH] load assets and bitmaps directly from apk --- src/api-impl-jni/util.c | 8 ++++ src/api-impl-jni/util.h | 5 +++ .../android/content/res/AssetManager.java | 43 +++++++++++++++++-- src/api-impl/android/graphics/Bitmap.java | 18 +++++++- src/main-executable/main.c | 2 + 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 035a6f6e..f241312f 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -103,4 +103,12 @@ void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class) handle_cache.view.setLayoutParams = _METHOD(handle_cache.view.class, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V"); if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); + + handle_cache.asset_manager.class = _REF((*env)->FindClass(env, "android/content/res/AssetManager")); + handle_cache.asset_manager.extractFromAPK = _STATIC_METHOD(handle_cache.asset_manager.class, "extractFromAPK", "(Ljava/lang/String;Ljava/lang/String;)V"); +} + +void extract_from_apk(const char *path, const char *target) { + JNIEnv *env = get_jni_env(); + (*env)->CallStaticObjectMethod(env, handle_cache.asset_manager.class, handle_cache.asset_manager.extractFromAPK, _JSTRING(path), _JSTRING(target)); } diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index 2fd4a23f..911a8ada 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -69,6 +69,10 @@ struct handle_cache { jclass class; jmethodID setLayoutParams; } view; + struct { + jclass class; + jmethodID extractFromAPK; + } asset_manager; }; extern struct handle_cache handle_cache; @@ -76,5 +80,6 @@ extern struct handle_cache handle_cache; const char * attribute_set_get_string(JNIEnv *env, jobject attrs, char *attribute, char *schema); int attribute_set_get_int(JNIEnv *env, jobject attrs, char *attribute, char *schema, int default_value); void set_up_handle_cache(JNIEnv *env, char *apk_main_activity_class); +void extract_from_apk(const char *path, const char *target); #endif diff --git a/src/api-impl/android/content/res/AssetManager.java b/src/api-impl/android/content/res/AssetManager.java index 3741ec95..6bebbde8 100644 --- a/src/api-impl/android/content/res/AssetManager.java +++ b/src/api-impl/android/content/res/AssetManager.java @@ -27,16 +27,23 @@ import org.xmlpull.v1.XmlPullParserFactory; import java.io.FileReader; +import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.Trace; import android.util.Log; import android.util.TypedValue; import java.util.ArrayList; import java.io.FileDescriptor; +import java.util.Enumeration; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; /** * Provides access to an application's raw asset files; see {@link Resources} @@ -326,7 +333,8 @@ public final class AssetManager { * @see #list */ public final InputStream open(String fileName, int accessMode) throws IOException { - int asset; + int asset; + // try loading from filesystem synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); @@ -338,7 +346,8 @@ public final class AssetManager { return res; } } - throw new FileNotFoundException("Asset file: " + fileName + ", errno: " + asset); + // alternatively load directly from APK + return ClassLoader.getSystemClassLoader().getResourceAsStream("assets/" + fileName); } public final AssetFileDescriptor openFd(String fileName) @@ -424,7 +433,8 @@ public final class AssetManager { * @param accessMode Desired access mode for retrieving the data. */ public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { - int asset; + int asset; + // try loading from filesystem synchronized (this) { if (!mOpen) { throw new RuntimeException("Assetmanager has been closed"); @@ -436,7 +446,8 @@ public final class AssetManager { return res; } } - throw new FileNotFoundException("Asset absolute file: " + fileName + ", errno: " + asset); + // alternatively load directly from APK + return ClassLoader.getSystemClassLoader().getResourceAsStream(fileName); } public final AssetFileDescriptor openNonAssetFd(String fileName) @@ -663,6 +674,30 @@ public final class AssetManager { return cookies; } + private static void extractFromAPK(String path, String target) throws IOException { + if (path.endsWith("/")) { // directory + try (JarFile apk = new JarFile(Context.this_application.getPackageCodePath())) { + Enumeration entries = apk.entries(); + while(entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().startsWith(path)) { + extractFromAPK(entry.getName(), entry.getName().replace(path, target)); + } + } + } + } else { // single file + Path file = Paths.get(android.os.Environment.getExternalStorageDirectory().getPath(), target); + if (!Files.exists(file)) { + try(InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(path)) { + if(inputStream != null) { + Files.createDirectories(file.getParent()); + Files.copy(inputStream, file); + } + } + } + } + } + /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to diff --git a/src/api-impl/android/graphics/Bitmap.java b/src/api-impl/android/graphics/Bitmap.java index 759739b8..178b99d6 100644 --- a/src/api-impl/android/graphics/Bitmap.java +++ b/src/api-impl/android/graphics/Bitmap.java @@ -19,10 +19,15 @@ package android.graphics; import android.util.DisplayMetrics; import java.io.OutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.Path; public final class Bitmap { /** @@ -111,7 +116,18 @@ public final class Bitmap { } // FIXME Bitmap(String path) { - pixbuf = native_bitmap_from_path(android.os.Environment.getExternalStorageDirectory().getPath() + "/" + path); + Path file = Paths.get(android.os.Environment.getExternalStorageDirectory().getPath(), path); + if (!Files.exists(file)) { + try (InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream(path)) { + if (inputStream != null) { + Files.createDirectories(file.getParent()); + Files.copy(inputStream, file); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + pixbuf = native_bitmap_from_path(file.toString()); mIsMutable = false; mIsPremultiplied = false; diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 5ff6b384..32c6719f 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -330,6 +330,8 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h jmethodID loadLibrary_with_classloader = _METHOD(java_runtime_class, "loadLibrary", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"); (*env)->CallVoidMethod(env, java_runtime, loadLibrary_with_classloader, _JSTRING("translation_layer_main"), class_loader); + extract_from_apk("assets/", "assets/"); + /* -- run the main activity's onCreate -- */ (*env)->CallVoidMethod(env, handle_cache.apk_main_activity.object, handle_cache.apk_main_activity.onCreate, NULL);