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 getNativePtr()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 Key(FileDescriptor fd, String path, boolean system, boolean load_as_shared_library, boolean overlay)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 equals(Object o)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 hashCode()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 { call()111 ApkAssets call(); 112 } 113 getFromCacheOrLoad(Key key, ApkAssetMaker callable)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 loadFromPath(@onNull String path)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 loadFromPath(String path, boolean system)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 loadFromPath(@onNull String path, boolean system, boolean forceSharedLibrary)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 loadFromFd(FileDescriptor fd, String friendlyName, boolean system, boolean forceSharedLibrary)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 loadOverlayFromPath(@onNull String idmapPath, boolean system)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 nativeLoad(String java_path, boolean system, boolean force_shared_lib, boolean overlay)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 nativeLoadFromFd(FileDescriptor file_descriptor, String friendly_name, boolean system, boolean force_shared_lib)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 nativeGetAssetPath(long ptr)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 nativeGetStringBlock(long ptr)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 nativeIsUpToDate(long ptr)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 nativeOpenXml(long ptr, String file_name)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