
org.robolectric.shadows.ShadowArscApkAssets9 Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static org.robolectric.res.android.Errors.NO_ERROR;
import static org.robolectric.res.android.Util.ATRACE_NAME;
import static org.robolectric.res.android.Util.JNI_TRUE;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.NonNull;
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Objects;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.res.android.Asset;
import org.robolectric.res.android.CppApkAssets;
import org.robolectric.res.android.Registries;
import org.robolectric.res.android.ResXMLTree;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowApkAssets.Picker;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp
/** Shadow for {@link ApkAssets} for Android P+ */
@Implements(
value = ApkAssets.class,
minSdk = P,
shadowPicker = Picker.class,
isInAndroidSdk = false,
looseSignatures = true)
public class ShadowArscApkAssets9 extends ShadowApkAssets {
// #define ATRACE_TAG ATRACE_TAG_RESOURCES
//
// #include "android-base/macros.h"
// #include "android-base/stringprintf.h"
// #include "android-base/unique_fd.h"
// #include "androidfw/ApkAssets.h"
// #include "utils/misc.h"
// #include "utils/Trace.h"
//
// #include "core_jni_helpers.h"
// #include "jni.h"
// #include "nativehelper/ScopedUtfChars.h"
//
// using ::android::base::unique_fd;
//
// namespace android {
// TODO: just use the ApkAssets constants. For some unknown reason these cannot be found
private static final int PROPERTY_SYSTEM = 1 << 0;
private static final int PROPERTY_DYNAMIC = 1 << 1;
private static final int PROPERTY_OVERLAY = 1 << 3;
protected static final String FRAMEWORK_APK_PATH =
ReflectionHelpers.getStaticField(AssetManager.class, "FRAMEWORK_APK_PATH");
private static final HashMap> cachedApkAssets =
new HashMap<>();
private static final HashMap cachedNativePtrs = new HashMap<>();
@RealObject private ApkAssets realApkAssets;
long getNativePtr() {
return reflector(_ApkAssets_.class, realApkAssets).getNativePtr();
}
/** Reflector interface for {@link ApkAssets}'s internals. */
@ForType(ApkAssets.class)
interface _ApkAssets_ {
@Static
@Direct
ApkAssets loadFromPath(String path);
@Static
@Direct
ApkAssets loadFromPath(String finalPath, boolean system);
@Static
@Direct
ApkAssets loadFromPath(String path, boolean system, boolean forceSharedLibrary);
@Static
@Direct
ApkAssets loadFromPath(String finalPath, int flags);
@Static
@Direct
ApkAssets loadFromPath(
FileDescriptor fd, String friendlyName, boolean system, boolean forceSharedLibrary);
@Accessor("mNativePtr")
long getNativePtr();
}
/**
* Caching key for {@link ApkAssets}.
*/
protected static class Key {
private final FileDescriptor fd;
private final String path;
private final boolean system;
private final boolean load_as_shared_library;
private final boolean overlay;
public Key(
FileDescriptor fd,
String path,
boolean system,
boolean load_as_shared_library,
boolean overlay) {
this.fd = fd;
this.path = path;
this.system = system;
this.load_as_shared_library = load_as_shared_library;
this.overlay = overlay;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key = (Key) o;
return system == key.system &&
load_as_shared_library == key.load_as_shared_library &&
overlay == key.overlay &&
Objects.equals(fd, key.fd) &&
Objects.equals(path, key.path);
}
@Override
public int hashCode() {
return Objects.hash(fd, path, system, load_as_shared_library, overlay);
}
}
@FunctionalInterface
protected interface ApkAssetMaker {
ApkAssets call();
}
protected static ApkAssets getFromCacheOrLoad(Key key, ApkAssetMaker callable) {
synchronized (cachedApkAssets) {
WeakReference cachedRef = cachedApkAssets.get(key);
ApkAssets apkAssets;
if (cachedRef != null) {
apkAssets = cachedRef.get();
if (apkAssets != null) {
return apkAssets;
} else {
cachedApkAssets.remove(key);
long nativePtr = cachedNativePtrs.remove(key);
Registries.NATIVE_APK_ASSETS_REGISTRY.unregister(nativePtr);
}
}
apkAssets = callable.call();
cachedApkAssets.put(key, new WeakReference<>(apkAssets));
long nativePtr = ((ShadowArscApkAssets9) Shadow.extract(apkAssets)).getNativePtr();
cachedNativePtrs.put(key, nativePtr);
return apkAssets;
}
}
@Implementation
protected static ApkAssets loadFromPath(@NonNull String path) throws IOException {
return getFromCacheOrLoad(
new Key(null, path, false, false, false),
() -> reflector(_ApkAssets_.class).loadFromPath(path));
}
/**
* Necessary to shadow this method because the framework path is hard-coded. Called from
* AssetManager.createSystemAssetsInZygoteLocked() in P+.
*/
@Implementation(maxSdk = Q)
protected static ApkAssets loadFromPath(String path, boolean system) throws IOException {
System.out.println(
"Called loadFromPath("
+ path
+ ", "
+ system
+ "); mode="
+ (RuntimeEnvironment.useLegacyResources() ? "legacy" : "binary")
+ " sdk="
+ RuntimeEnvironment.getApiLevel());
if (FRAMEWORK_APK_PATH.equals(path)) {
path = RuntimeEnvironment.getAndroidFrameworkJarPath().toString();
}
String finalPath = path;
return getFromCacheOrLoad(
new Key(null, path, system, false, false),
() -> reflector(_ApkAssets_.class).loadFromPath(finalPath, system));
}
@Implementation(maxSdk = Q)
@NonNull
protected static ApkAssets loadFromPath(
@NonNull String path, boolean system, boolean forceSharedLibrary) throws IOException {
return getFromCacheOrLoad(
new Key(null, path, system, forceSharedLibrary, false),
() -> reflector(_ApkAssets_.class).loadFromPath(path, system, forceSharedLibrary));
}
@Implementation(minSdk = R)
protected static ApkAssets loadFromPath(String path, int flags) throws IOException {
boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
if (FRAMEWORK_APK_PATH.equals(path)) {
path = RuntimeEnvironment.getAndroidFrameworkJarPath().toString();
}
String finalPath = path;
return getFromCacheOrLoad(
new Key(null, path, system, false, false),
() -> reflector(_ApkAssets_.class).loadFromPath(finalPath, flags));
}
@Implementation(maxSdk = Q)
protected static ApkAssets loadFromFd(
FileDescriptor fd, String friendlyName, boolean system, boolean forceSharedLibrary)
throws IOException {
return getFromCacheOrLoad(
new Key(fd, friendlyName, system, forceSharedLibrary, false),
() ->
reflector(_ApkAssets_.class)
.loadFromPath(fd, friendlyName, system, forceSharedLibrary));
}
// static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
// jboolean force_shared_lib, jboolean overlay) {
@Implementation(maxSdk = Q)
protected static long nativeLoad(
String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException {
if (path == null) {
return 0;
}
ATRACE_NAME(String.format("LoadApkAssets(%s)", path));
CppApkAssets apk_assets;
try {
if (overlay) {
apk_assets = CppApkAssets.LoadOverlay(path, system);
} else if (forceSharedLib) {
apk_assets = CppApkAssets.LoadAsSharedLibrary(path, system);
} else {
apk_assets = CppApkAssets.Load(path, system);
}
} catch (OutOfMemoryError e) {
OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path);
outOfMemoryError.initCause(e);
throw outOfMemoryError;
}
if (apk_assets == null) {
String error_msg = String.format("Failed to load asset path %s", path);
throw new IOException(error_msg);
}
return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets);
}
@Implementation(minSdk = R)
protected static Object nativeLoad(
Object format, Object javaPath, Object flags, Object assetsProvider) throws IOException {
boolean system = ((int) flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
boolean overlay = ((int) flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
boolean forceSharedLib = ((int) flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
return nativeLoad((String) javaPath, system, forceSharedLib, overlay);
}
// static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
// jstring friendly_name, jboolean system, jboolean
// force_shared_lib) {
@Implementation(maxSdk = Q)
protected static long nativeLoadFromFd(
FileDescriptor file_descriptor,
String friendly_name,
boolean system,
boolean force_shared_lib) {
String friendly_name_utf8 = friendly_name;
if (friendly_name_utf8 == null) {
return 0;
}
throw new UnsupportedOperationException();
// ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8));
//
// int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
// if (fd < 0) {
// throw new IllegalArgumentException("Bad FileDescriptor");
// }
//
// unique_fd dup_fd(.dup(fd));
// if (dup_fd < 0) {
// throw new IOException(errno);
// return 0;
// }
//
// ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd),
// friendly_name_utf8,
// system, force_shared_lib);
// if (apk_assets == null) {
// String error_msg = String.format("Failed to load asset path %s from fd %d",
// friendly_name_utf8, dup_fd.get());
// throw new IOException(error_msg);
// return 0;
// }
// return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
}
// static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
@Implementation
protected static String nativeGetAssetPath(long ptr) {
CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
return apk_assets.GetPath();
}
// static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
@Implementation
protected static long nativeGetStringBlock(long ptr) {
CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
return apk_assets.GetLoadedArsc().GetStringPool().getNativePtr();
}
// static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
@Implementation
protected static boolean nativeIsUpToDate(long ptr) {
// (void)apk_assets;
return JNI_TRUE;
}
// static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@Implementation
protected static long nativeOpenXml(long ptr, String file_name) throws FileNotFoundException {
String path_utf8 = file_name;
if (path_utf8 == null) {
return 0;
}
CppApkAssets apk_assets =
Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
Asset asset = apk_assets.Open(path_utf8, Asset.AccessMode.ACCESS_RANDOM);
if (asset == null) {
throw new FileNotFoundException(path_utf8);
}
// DynamicRefTable is only needed when looking up resource references. Opening an XML file
// directly from an ApkAssets has no notion of proper resource references.
ResXMLTree xml_tree = new ResXMLTree(null); // util.make_unique(nullptr /*dynamicRefTable*/);
int err = xml_tree.setTo(asset.getBuffer(true), (int) asset.getLength(), true);
// asset.reset();
if (err != NO_ERROR) {
throw new FileNotFoundException("Corrupt XML binary file");
}
return Registries.NATIVE_RES_XML_TREES.register(xml_tree); // reinterpret_cast(xml_tree.release());
}
// // JNI registration.
// static const JNINativeMethod gApkAssetsMethods[] = {
// {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
// {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
// (void*)NativeLoadFromFd},
// {"nativeDestroy", "(J)V", (void*)NativeDestroy},
// {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
// {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
// {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
// {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
// };
//
// int register_android_content_res_ApkAssets(JNIEnv* env) {
// return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
// arraysize(gApkAssetsMethods));
// }
//
// } // namespace android
@Implementation(minSdk = S)
protected void close() {}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy