Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static org.robolectric.res.android.Errors.NO_ERROR;
      4 import static org.robolectric.res.android.Util.ATRACE_NAME;
      5 import static org.robolectric.res.android.Util.JNI_TRUE;
      6 import static org.robolectric.shadow.api.Shadow.directlyOn;
      7 
      8 import android.annotation.NonNull;
      9 import android.content.res.ApkAssets;
     10 import android.content.res.AssetManager;
     11 import android.os.Build;
     12 import java.io.FileDescriptor;
     13 import java.io.FileNotFoundException;
     14 import java.io.IOException;
     15 import java.lang.ref.WeakReference;
     16 import java.util.HashMap;
     17 import java.util.Objects;
     18 import org.robolectric.RuntimeEnvironment;
     19 import org.robolectric.annotation.Implementation;
     20 import org.robolectric.annotation.Implements;
     21 import org.robolectric.annotation.RealObject;
     22 import org.robolectric.res.android.Asset;
     23 import org.robolectric.res.android.CppApkAssets;
     24 import org.robolectric.res.android.Registries;
     25 import org.robolectric.res.android.ResXMLTree;
     26 import org.robolectric.shadow.api.Shadow;
     27 import org.robolectric.shadows.ShadowApkAssets.Picker;
     28 import org.robolectric.util.ReflectionHelpers;
     29 import org.robolectric.util.ReflectionHelpers.ClassParameter;
     30 
     31 
     32 // transliterated from
     33 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp
     34 
     35 @Implements(value = ApkAssets.class, minSdk = Build.VERSION_CODES.P,
     36     shadowPicker = Picker.class, isInAndroidSdk = false)
     37 public class ShadowArscApkAssets9 extends ShadowApkAssets {
     38 // #define ATRACE_TAG ATRACE_TAG_RESOURCES
     39 //
     40 // #include "android-base/macros.h"
     41 // #include "android-base/stringprintf.h"
     42 // #include "android-base/unique_fd.h"
     43 // #include "androidfw/ApkAssets.h"
     44 // #include "utils/misc.h"
     45 // #include "utils/Trace.h"
     46 //
     47 // #include "core_jni_helpers.h"
     48 // #include "jni.h"
     49 // #include "nativehelper/ScopedUtfChars.h"
     50 //
     51 // using ::android::base::unique_fd;
     52 //
     53 // namespace android {
     54 
     55   private static final String FRAMEWORK_APK_PATH =
     56       ReflectionHelpers.getStaticField(AssetManager.class, "FRAMEWORK_APK_PATH");
     57 
     58   private static final HashMap<Key, WeakReference<ApkAssets>> cachedApkAssets =
     59       new HashMap<>();
     60   private static final HashMap<Key, Long> cachedNativePtrs = new HashMap<>();
     61 
     62   @RealObject private ApkAssets realApkAssets;
     63 
     64   long getNativePtr() {
     65     return ReflectionHelpers.getField(realApkAssets, "mNativePtr");
     66   }
     67 
     68   /**
     69    * Caching key for {@link ApkAssets}.
     70    */
     71   private static class Key {
     72     private final FileDescriptor fd;
     73     private final String path;
     74     private final boolean system;
     75     private final boolean load_as_shared_library;
     76     private final boolean overlay;
     77 
     78     public Key(FileDescriptor fd, String path, boolean system, boolean load_as_shared_library,
     79         boolean overlay) {
     80       this.fd = fd;
     81       this.path = path;
     82       this.system = system;
     83       this.load_as_shared_library = load_as_shared_library;
     84       this.overlay = overlay;
     85     }
     86 
     87     @Override
     88     public boolean equals(Object o) {
     89       if (this == o) {
     90         return true;
     91       }
     92       if (o == null || getClass() != o.getClass()) {
     93         return false;
     94       }
     95       Key key = (Key) o;
     96       return system == key.system &&
     97           load_as_shared_library == key.load_as_shared_library &&
     98           overlay == key.overlay &&
     99           Objects.equals(fd, key.fd) &&
    100           Objects.equals(path, key.path);
    101     }
    102 
    103     @Override
    104     public int hashCode() {
    105       return Objects.hash(fd, path, system, load_as_shared_library, overlay);
    106     }
    107   }
    108 
    109   @FunctionalInterface
    110   private interface ApkAssetMaker {
    111     ApkAssets call();
    112   }
    113 
    114   private static ApkAssets getFromCacheOrLoad(Key key, ApkAssetMaker callable) {
    115     synchronized (cachedApkAssets) {
    116       WeakReference<ApkAssets> cachedRef = cachedApkAssets.get(key);
    117       ApkAssets apkAssets;
    118       if (cachedRef != null) {
    119         apkAssets = cachedRef.get();
    120         if (apkAssets != null) {
    121           return apkAssets;
    122         } else {
    123           cachedApkAssets.remove(key);
    124           long nativePtr = cachedNativePtrs.remove(key);
    125           Registries.NATIVE_APK_ASSETS_REGISTRY.unregister(nativePtr);
    126         }
    127       }
    128 
    129       apkAssets = callable.call();
    130       long nativePtr = ((ShadowArscApkAssets9) Shadow.extract(apkAssets)).getNativePtr();
    131       cachedNativePtrs.put(key, nativePtr);
    132       cachedApkAssets.put(key, new WeakReference<>(apkAssets));
    133       return apkAssets;
    134     }
    135   }
    136 
    137   @Implementation
    138   protected static ApkAssets loadFromPath(@NonNull String path) throws IOException {
    139     return getFromCacheOrLoad(
    140         new Key(null, path, false, false, false),
    141         () -> directlyOn(ApkAssets.class, "loadFromPath", ClassParameter.from(String.class, path)));
    142   }
    143 
    144   /**
    145    * Necessary to shadow this method because the framework path is hard-coded.
    146    * Called from AssetManager.createSystemAssetsInZygoteLocked() in P+.
    147    */
    148   @Implementation
    149   protected static ApkAssets loadFromPath(String path, boolean system)
    150       throws IOException {
    151     System.out.println(
    152         "Called loadFromPath("
    153             + path
    154             + ", " + system + "); mode="
    155             + (RuntimeEnvironment.useLegacyResources() ? "legacy" : "binary")
    156             + " sdk=" + RuntimeEnvironment.getApiLevel());
    157 
    158     if (FRAMEWORK_APK_PATH.equals(path)) {
    159       path = RuntimeEnvironment.getAndroidFrameworkJarPath();
    160     }
    161 
    162     String finalPath = path;
    163     return getFromCacheOrLoad(
    164         new Key(null, path, system, false, false),
    165         () -> directlyOn(ApkAssets.class, "loadFromPath",
    166             ClassParameter.from(String.class, finalPath),
    167             ClassParameter.from(boolean.class, system)));
    168   }
    169 
    170   @Implementation
    171   protected static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
    172       boolean forceSharedLibrary) throws IOException {
    173     return getFromCacheOrLoad(
    174         new Key(null, path, system, forceSharedLibrary, false),
    175         () -> directlyOn(ApkAssets.class, "loadFromPath",
    176             ClassParameter.from(String.class, path),
    177             ClassParameter.from(boolean.class, system),
    178             ClassParameter.from(boolean.class, forceSharedLibrary)));
    179   }
    180 
    181   @Implementation
    182   protected static ApkAssets loadFromFd(FileDescriptor fd,
    183       String friendlyName, boolean system, boolean forceSharedLibrary)
    184       throws IOException {
    185     return getFromCacheOrLoad(
    186         new Key(fd, friendlyName, system, forceSharedLibrary, false),
    187         () -> directlyOn(ApkAssets.class, "loadFromPath",
    188             ClassParameter.from(FileDescriptor.class, fd),
    189             ClassParameter.from(String.class, friendlyName),
    190             ClassParameter.from(boolean.class, system),
    191             ClassParameter.from(boolean.class, forceSharedLibrary)));
    192   }
    193 
    194   @Implementation
    195   protected static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
    196       throws IOException {
    197     throw new UnsupportedOperationException();
    198     // return getFromCacheOrLoad(
    199         // new Key(fd, friendlyName, system, forceSharedLibrary, false),
    200         // () -> directlyOn(ApkAssets.class, "loadFromPath",
    201         //     ClassParameter.from(FileDescriptor.class, fd),
    202         //     ClassParameter.from(String.class, friendlyName),
    203         //     ClassParameter.from(boolean.class, system),
    204         //     ClassParameter.from(boolean.class, forceSharedLibrary)));
    205   }
    206 
    207   // static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
    208 //                         jboolean force_shared_lib, jboolean overlay) {
    209   @Implementation
    210   protected static long nativeLoad(String java_path, boolean system,
    211       boolean force_shared_lib, boolean overlay) throws IOException {
    212     String path = java_path;
    213     if (path == null) {
    214       return 0;
    215     }
    216 
    217     ATRACE_NAME(String.format("LoadApkAssets(%s)", path));
    218 
    219     CppApkAssets apk_assets;
    220     try {
    221       if (overlay) {
    222         apk_assets = CppApkAssets.LoadOverlay(path, system);
    223       } else if (force_shared_lib) {
    224         apk_assets =
    225             CppApkAssets.LoadAsSharedLibrary(path, system);
    226       } else {
    227         apk_assets = CppApkAssets.Load(path, system);
    228       }
    229     } catch (OutOfMemoryError e) {
    230       OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path);
    231       outOfMemoryError.initCause(e);
    232       throw outOfMemoryError;
    233     }
    234 
    235     if (apk_assets == null) {
    236       String error_msg = String.format("Failed to load asset path %s", path);
    237       throw new IOException(error_msg);
    238     }
    239     return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets);
    240   }
    241 
    242   // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
    243 //                               jstring friendly_name, jboolean system, jboolean force_shared_lib) {
    244   @Implementation
    245   protected static long nativeLoadFromFd(FileDescriptor file_descriptor,
    246       String friendly_name, boolean system, boolean force_shared_lib) {
    247     String friendly_name_utf8 = friendly_name;
    248     if (friendly_name_utf8 == null) {
    249       return 0;
    250     }
    251 
    252     throw new UnsupportedOperationException();
    253     // ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8));
    254     //
    255     // int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
    256     // if (fd < 0) {
    257     //   throw new IllegalArgumentException("Bad FileDescriptor");
    258     // }
    259     //
    260     // unique_fd dup_fd(.dup(fd));
    261     // if (dup_fd < 0) {
    262     //   throw new IOException(errno);
    263     //   return 0;
    264     // }
    265     //
    266     // ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd),
    267     //                                                                     friendly_name_utf8,
    268     //                                                                     system, force_shared_lib);
    269     // if (apk_assets == null) {
    270     //   String error_msg = String.format("Failed to load asset path %s from fd %d",
    271     //                                              friendly_name_utf8, dup_fd.get());
    272     //   throw new IOException(error_msg);
    273     //   return 0;
    274     // }
    275     // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
    276   }
    277 
    278   // static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
    279   @Implementation
    280   protected static String nativeGetAssetPath(long ptr) {
    281     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
    282     return apk_assets.GetPath();
    283   }
    284 
    285   // static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
    286   @Implementation
    287   protected static long nativeGetStringBlock(long ptr) {
    288     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
    289     return apk_assets.GetLoadedArsc().GetStringPool().getNativePtr();
    290   }
    291 
    292   // static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
    293   @Implementation
    294   protected static boolean nativeIsUpToDate(long ptr) {
    295     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
    296     // (void)apk_assets;
    297     return JNI_TRUE;
    298   }
    299 
    300   // static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
    301   @Implementation
    302   protected static long nativeOpenXml(long ptr, String file_name) throws FileNotFoundException {
    303     String path_utf8 = file_name;
    304     if (path_utf8 == null) {
    305       return 0;
    306     }
    307 
    308     CppApkAssets apk_assets =
    309         Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
    310     Asset asset = apk_assets.Open(path_utf8,
    311         Asset.AccessMode.ACCESS_RANDOM);
    312     if (asset == null) {
    313       throw new FileNotFoundException(path_utf8);
    314     }
    315 
    316     // DynamicRefTable is only needed when looking up resource references. Opening an XML file
    317     // directly from an ApkAssets has no notion of proper resource references.
    318     ResXMLTree xml_tree = new ResXMLTree(null); // util.make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
    319     int err = xml_tree.setTo(asset.getBuffer(true), (int) asset.getLength(), true);
    320     // asset.reset();
    321 
    322     if (err != NO_ERROR) {
    323       throw new FileNotFoundException("Corrupt XML binary file");
    324     }
    325     return Registries.NATIVE_RES_XML_TREES.register(xml_tree); // reinterpret_cast<jlong>(xml_tree.release());
    326   }
    327 
    328 // // JNI registration.
    329 // static const JNINativeMethod gApkAssetsMethods[] = {
    330 //     {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
    331 //     {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
    332 //         (void*)NativeLoadFromFd},
    333 //     {"nativeDestroy", "(J)V", (void*)NativeDestroy},
    334 //     {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
    335 //     {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
    336 //     {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
    337 //     {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
    338 // };
    339 //
    340 // int register_android_content_res_ApkAssets(JNIEnv* env) {
    341 //   return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
    342 //                               arraysize(gApkAssetsMethods));
    343 // }
    344 //
    345 // }  // namespace android
    346 }
    347