diff --git a/meson.build b/meson.build index 834125b7..816a071e 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,9 @@ libdl_bio_dep = [ libskia_dep = [ cc.find_library('SkiaSharp') ] +libandroidfw_dep = [ + cc.find_library('androidfw', dirs : [ '/usr' / get_option('libdir') / 'art', '/usr/local' / get_option('libdir') / 'art', get_option('prefix') / get_option('libdir') / 'art' ]), +] if fs.is_file('/usr' / get_option('libdir') / 'java/core-all_classes.jar') bootclasspath = '/usr' / get_option('libdir') / 'java/core-all_classes.jar' elif fs.is_file('/usr/local' / get_option('libdir') / 'java/core-all_classes.jar') @@ -117,7 +120,8 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ install_dir : get_option('libdir') / 'java/dex/android_translation_layer/natives', dependencies: [ dependency('gtk4', version: '>=4.8'), dependency('gl'), dependency('egl'), dependency('wayland-client'), dependency('jni'), - dependency('libportal'), dependency('sqlite3'), libskia_dep, dependency('libavcodec', version: '>=59'), dependency('libdrm') + dependency('libportal'), dependency('sqlite3'), libskia_dep, dependency('libavcodec', version: '>=59'), dependency('libdrm'), + libandroidfw_dep ], link_with: [ libandroid_so ], link_args: [ diff --git a/src/api-impl-jni/android_content_res_AssetManager.c b/src/api-impl-jni/android_content_res_AssetManager.c index 2f57564d..abc191f4 100644 --- a/src/api-impl-jni/android_content_res_AssetManager.c +++ b/src/api-impl-jni/android_content_res_AssetManager.c @@ -1,10 +1,13 @@ #include #include +#include #include #include #include #include +#include + #include "defines.h" #include "util.h" #include "generated_headers/android_content_res_AssetManager.h" @@ -107,3 +110,117 @@ JNIEXPORT void JNICALL Java_android_content_res_AssetManager_destroyAsset(JNIEnv printf("closing asset with fd: %d\n", fd); close(fd); } + +JNIEXPORT void JNICALL Java_android_content_res_AssetManager_init(JNIEnv *env, jobject this) +{ + struct AssetManager *asset_manager = AssetManager_new(); + const struct ResTable_config config = { + .density = /*ACONFIGURATION_DENSITY_MEDIUM*/ 160, + .sdkVersion = 24, + }; + AssetManager_setConfiguration(asset_manager, &config, NULL); + _SET_LONG_FIELD(this, "mObject", _INTPTR(asset_manager)); +} + +JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_addAssetPathNative(JNIEnv *env, jobject this, jstring path) +{ + int32_t cookie; + struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject")); + AssetManager_addAssetPath(asset_manager, _CSTRING(path), &cookie, false, false); + return cookie; +} + +JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_loadResourceValue(JNIEnv *env, jobject this, jint ident, jshort density, jobject outValue, jboolean resolve) +{ + struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject")); + const struct ResTable *res_table = AssetManager_getResources(asset_manager, true); + uint32_t resId = ident; + struct Res_value value; + uint32_t outSpecFlags; + struct ResTable_config outConfig; + ssize_t block = ResTable_getResource(res_table, resId, &value, false, density, &outSpecFlags, &outConfig); + if (resolve) { + block = ResTable_resolveReference(res_table, &value, block, &resId, &outSpecFlags, &outConfig); + } + if (block >= 0) { + _SET_INT_FIELD(outValue, "type", value.dataType); + _SET_INT_FIELD(outValue, "data", value.data); + _SET_INT_FIELD(outValue, "resourceId", resId); + if (value.dataType == TYPE_STRING) { + const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, block); + size_t len; + const char16_t *string = ResStringPool_stringAt(string_pool, value.data, &len); + _SET_OBJ_FIELD(outValue, "string", "Ljava/lang/CharSequence;", (*env)->NewString(env, string, len)); + } else { + _SET_OBJ_FIELD(outValue, "string", "Ljava/lang/CharSequence;", NULL); + } + } + return block; +} + +JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getArrayStringResource(JNIEnv *env, jobject this, jint ident) +{ + int i; + struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject")); + const struct ResTable *res_table = AssetManager_getResources(asset_manager, true); + const struct bag_entry *bag; + int bag_count = ResTable_lockBag(res_table, ident, &bag); + jobjectArray array = (*env)->NewObjectArray(env, bag_count, (*env)->FindClass(env, "java/lang/String"), NULL); + for (i = 0; i < bag_count; i++) { + if (bag[i].map.value.dataType == TYPE_STRING) { + const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, bag[i].stringBlock); + if (string_pool == NULL) + continue; + size_t len; + const char16_t *string = ResStringPool_stringAt(string_pool, bag[i].map.value.data, &len); + (*env)->SetObjectArrayElement(env, array, i, (*env)->NewString(env, string, len)); + } + } + + ResTable_unlockBag(res_table, bag); + return array; +} + +JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_getResourceIdentifier(JNIEnv *env, jobject this, jstring name, jstring defType, jstring defPackage) +{ + const char16_t *name16 = NULL; + const char16_t *defType16 = NULL; + const char16_t *defPackage16 = NULL; + int name_len = 0; + int defType_len = 0; + int defPackage_len = 0; + int ret; + + struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject")); + const struct ResTable *res_table = AssetManager_getResources(asset_manager, true); + if (name) { + name16 = (*env)->GetStringChars(env, name, NULL); + name_len = (*env)->GetStringLength(env, name); + } + if (defType) { + defType16 = (*env)->GetStringChars(env, defType, NULL); + defType_len = (*env)->GetStringLength(env, defType); + } + if (defPackage) { + defPackage16 = (*env)->GetStringChars(env, defPackage, NULL); + defPackage_len = (*env)->GetStringLength(env, defPackage); + } + ret = ResTable_identifierForName(res_table, name16, name_len, defType16, defType_len, defPackage16, defPackage_len, NULL); + if (name) + (*env)->ReleaseStringChars(env, name, name16); + if (defType) + (*env)->ReleaseStringChars(env, defType, defType16); + if (defPackage) + (*env)->ReleaseStringChars(env, defPackage, defPackage16); + return ret; +} + +JNIEXPORT jobject JNICALL Java_android_content_res_AssetManager_getPooledString(JNIEnv *env, jobject this, jint block, jint index) +{ + struct AssetManager *asset_manager = _PTR(_GET_LONG_FIELD(this, "mObject")); + const struct ResTable *res_table = AssetManager_getResources(asset_manager, true); + const struct ResStringPool *string_pool = ResTable_getTableStringBlock(res_table, block); + size_t len; + const char16_t *string = ResStringPool_stringAt(string_pool, index, &len); + return (*env)->NewString(env, string, len); +} diff --git a/src/api-impl-jni/generated_headers/android_content_res_AssetManager.h b/src/api-impl-jni/generated_headers/android_content_res_AssetManager.h index d7fe6c52..64058216 100644 --- a/src/api-impl-jni/generated_headers/android_content_res_AssetManager.h +++ b/src/api-impl-jni/generated_headers/android_content_res_AssetManager.h @@ -33,6 +33,14 @@ extern "C" { #define android_content_res_AssetManager_STYLE_CHANGING_CONFIGURATIONS 4L #undef android_content_res_AssetManager_STYLE_DENSITY #define android_content_res_AssetManager_STYLE_DENSITY 5L +/* + * Class: android_content_res_AssetManager + * Method: getPooledString + * Signature: (II)Ljava/lang/CharSequence; + */ +JNIEXPORT jobject JNICALL Java_android_content_res_AssetManager_getPooledString + (JNIEnv *, jobject, jint, jint); + /* * Class: android_content_res_AssetManager * Method: list @@ -73,6 +81,14 @@ JNIEXPORT void JNICALL Java_android_content_res_AssetManager_setLocale JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getLocales (JNIEnv *, jobject); +/* + * Class: android_content_res_AssetManager + * Method: getResourceIdentifier + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_android_content_res_AssetManager_getResourceIdentifier + (JNIEnv *, jobject, jstring, jstring, jstring); + /* * Class: android_content_res_AssetManager * Method: openAsset @@ -297,6 +313,14 @@ JNIEXPORT jobjectArray JNICALL Java_android_content_res_AssetManager_getArrayStr JNIEXPORT jintArray JNICALL Java_android_content_res_AssetManager_getArrayStringInfo (JNIEnv *, jobject, jint); +/* + * Class: android_content_res_AssetManager + * Method: init + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_android_content_res_AssetManager_init + (JNIEnv *, jobject); + /* * Class: android_content_res_AssetManager * Method: destroy diff --git a/src/api-impl/android/content/res/AssetManager.java b/src/api-impl/android/content/res/AssetManager.java index 3195a1c9..be43fb8d 100644 --- a/src/api-impl/android/content/res/AssetManager.java +++ b/src/api-impl/android/content/res/AssetManager.java @@ -102,7 +102,7 @@ public final class AssetManager { private final long[] mOffsets = new long[2]; // For communication with native code. - private int mObject; + private long mObject; private int mNObject; // used by the NDK private StringBlock mStringBlocks[] = null; @@ -121,18 +121,18 @@ public final class AssetManager { * {@hide} */ public AssetManager() { - try { - tableBlocks = new ArrayList<>(); - Enumeration resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc"); - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - if (!resource.getFile().contains("com.google.android.gms")) { // ignore MicroG .apk - tableBlocks.add(TableBlock.load(resource.openStream())); - } - } - } catch (IOException e) { - Log.e(TAG, "failed to load resources.arsc" + e); - } + tableBlocks = new ArrayList<>(); + // try { + // Enumeration resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc"); + // while (resources.hasMoreElements()) { + // URL resource = resources.nextElement(); + // if (!resource.getFile().contains("com.google.android.gms")) { // ignore MicroG .apk + // tableBlocks.add(TableBlock.load(resource.openStream())); + // } + // } + // } catch (IOException e) { + // Log.e(TAG, "failed to load resources.arsc" + e); + // } // FIXME: evaluate if this can be axed synchronized (this) { @@ -144,6 +144,19 @@ public final class AssetManager { if (localLOGV) Log.v(TAG, "New asset manager: " + this); // ensureSystemAssets() + try { + Enumeration resources = ClassLoader.getSystemClassLoader().getResources("resources.arsc"); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + String path = resource.getPath(); + if (!path.contains("com.google.android.gms")) { // ignore MicroG .apk + path = path.substring(path.indexOf("file:") + 5, path.indexOf("!/resources.arsc")); + addAssetPath(path); + } + } + } catch (IOException e) { + Log.e(TAG, "failed to load resources.arsc" + e); + } } } @@ -215,18 +228,9 @@ public final class AssetManager { /*package*/ final CharSequence getResourceText(int id) { if (id == 0) return ""; - ResValue resValue = tableBlockSearch(id).pickOne().getResValue(); - if (resValue.getValueType() == ValueType.REFERENCE) { - if(id == resValue.getData()) { - System.out.println("getResourceText: self-reference... returing \"\""); - return ""; - } - return getResourceText(resValue.getData()); - } - if (resValue.getValueType() == ValueType.INT_COLOR_RGB8) { - return String.format("#%08x", resValue.getData()); - } - return resValue.getDataAsPoolString().get(); + TypedValue value = new TypedValue(); + loadResourceValue(id, (short) 0, value, true); + return value.coerceToString(); } /** @@ -256,43 +260,15 @@ public final class AssetManager { * @param id Resource id of the string array */ /*package*/ final String[] getResourceStringArray(final int id) { - ArrayList values = new ArrayList(); - for (ResValueMap map : tableBlockSearch(id).pickOne().getResValueMapArray().getChildes()) { - if (map.getType() == TypedValue.TYPE_REFERENCE) { - values.add(String.valueOf(getResourceText(map.getData()))); - } else if (map.getType() == TypedValue.TYPE_STRING) { - values.add(map.getValueAsString()); - } else { - values.add("value of unknown type " + map.getType()); - } - } - return values.toArray(new String[0]); + return getArrayStringResource(id); } /*package*/ final boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { - /*int block = loadResourceValue(ident, (short) density, outValue, resolveRefs); - if (block >= 0) { - if (outValue.type != TypedValue.TYPE_STRING) { - return true; - } - outValue.string = mStringBlocks[block].get(outValue.data); - return true; - }*/ - EntryGroup entryGroup = tableBlockSearch(ident); - if (entryGroup == null) - return false; // not found - ResValue resValue = entryGroup.pickOne().getResValue(); - if (resValue == null) - return false; // not found - outValue.resourceId = ident; - outValue.type = resValue.getType(); - outValue.data = resValue.getData(); - if (outValue.type == TypedValue.TYPE_STRING) - outValue.string = getResourceText(ident); - return true; + int block = loadResourceValue(ident, (short) density, outValue, resolveRefs); + return block >= 0; } /** @@ -397,13 +373,7 @@ public final class AssetManager { } } - /*package*/ final CharSequence getPooledString(int block, int id) { - // System.out.println("Get pooled: block=" + block - // + ", id=#" + Integer.toHexString(id) - // + ", blocks=" + mStringBlocks); - TableString string = tableBlocks.get(block).getStringPool().get(id); - return string != null ? string.get() : null; - } + /*package*/ native final CharSequence getPooledString(int block, int id); /** * Open an asset using ACCESS_STREAMING mode. This provides access to @@ -844,24 +814,7 @@ public final class AssetManager { /** * Retrieve the resource identifier for the given resource name. */ - /*package*/ /*native*/ final int getResourceIdentifier(String name, String type, String defPackage) { - System.out.println("getResourceIdentifier(" + name + "," + type + "," + defPackage + ") called"); - for (TableBlock tableBlock : tableBlocks) { - for (PackageBlock packageBlock : tableBlock.listPackages()) { - if (packageBlock.getName().equals(defPackage)) { - Entry entry = packageBlock.getEntry("", type, name); - if(entry != null) { - return entry.getResourceId(); - } else { - return -1; // TODO: investigate - } - } - } - } - - // package not found - return -1; - } + /*package*/ native final int getResourceIdentifier(String name, String type, String defPackage); /*package*/ /*native*/ final String getResourceName(int resid) { return tableBlockSearch(resid).pickOne().getName(); @@ -944,12 +897,10 @@ public final class AssetManager { outValues[d+AssetManager.STYLE_DATA] = 0; outValues[d+AssetManager.STYLE_ASSET_COOKIE] = 0; int resId = inAttrs[i]; - EntryGroup entryGroup = tableBlockSearch(resId); - if (entryGroup == null) - continue; - Entry entry = entryGroup.pickOne(); + EntryGroup entryGroup = null; + Entry entry = null; ValueItem valueItem = null; - while ("attr".equals(entry.getTypeName())) { + while (entry == null || "attr".equals(entry.getTypeName())) { valueItem = null; if (xmlCache.containsKey(resId)) { valueItem = parser.getResXmlAttributeAt(xmlCache.get(resId)); @@ -975,6 +926,17 @@ public final class AssetManager { if (valueItem == null) continue; if (valueItem.getValueType() == ValueType.REFERENCE) { + resId = valueItem.getData(); + TypedValue value = new TypedValue(); + int block = loadResourceValue(resId, (short)0, value, true); + if (block >= 0) { + outValues[d + AssetManager.STYLE_RESOURCE_ID] = value.resourceId; + outValues[d + AssetManager.STYLE_TYPE] = value.type; + outValues[d + AssetManager.STYLE_DATA] = value.data; + outValues[d + AssetManager.STYLE_ASSET_COOKIE] = block; + outIndices[++outIndices[0]] = i; + continue; + } while (valueItem.getValueType() == ValueType.REFERENCE) { resId = valueItem.getData(); if (resId == 0) @@ -1045,7 +1007,7 @@ public final class AssetManager { return values; } - private /* native */ final void init() {} + private native final void init(); private native final void destroy(); private final void incRefsLocked(int id) { diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 32b2b80b..8ce15479 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -314,8 +314,6 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h _SET_STATIC_INT_FIELD(display_class, "window_width", d->window_width); _SET_STATIC_INT_FIELD(display_class, "window_height", d->window_height); - set_up_handle_cache(env); - /* -- register our JNI library under the appropriate classloader -- */ /* 'android/view/View' is part of the "hax.dex" package, any other function from that package would serve just as well */ @@ -331,6 +329,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); + set_up_handle_cache(env); + /* -- misc -- */ // some apps need the apk path since they directly read their apk