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.content.res.loader.AssetsProvider; 12 import android.os.Build; 13 14 import org.robolectric.RuntimeEnvironment; 15 import org.robolectric.annotation.Implementation; 16 import org.robolectric.annotation.Implements; 17 import org.robolectric.annotation.RealObject; 18 import org.robolectric.res.android.Asset; 19 import org.robolectric.res.android.CppApkAssets; 20 import org.robolectric.res.android.Registries; 21 import org.robolectric.res.android.ResXMLTree; 22 import org.robolectric.shadow.api.Shadow; 23 import org.robolectric.shadows.ShadowApkAssets.Picker; 24 import org.robolectric.util.ReflectionHelpers; 25 import org.robolectric.util.ReflectionHelpers.ClassParameter; 26 27 import java.io.FileDescriptor; 28 import java.io.FileNotFoundException; 29 import java.io.IOException; 30 import java.lang.ref.WeakReference; 31 import java.util.HashMap; 32 import java.util.Objects; 33 34 35 // transliterated from 36 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp 37 38 @Implements(value = ApkAssets.class, minSdk = Build.VERSION_CODES.P, 39 shadowPicker = Picker.class, isInAndroidSdk = false) 40 public class ShadowArscApkAssets9 extends ShadowApkAssets { 41 // #define ATRACE_TAG ATRACE_TAG_RESOURCES 42 // 43 // #include "android-base/macros.h" 44 // #include "android-base/stringprintf.h" 45 // #include "android-base/unique_fd.h" 46 // #include "androidfw/ApkAssets.h" 47 // #include "utils/misc.h" 48 // #include "utils/Trace.h" 49 // 50 // #include "core_jni_helpers.h" 51 // #include "jni.h" 52 // #include "nativehelper/ScopedUtfChars.h" 53 // 54 // using ::android::base::unique_fd; 55 // 56 // namespace android { 57 // BEGIN-INTERNAL 58 private static final int PROPERTY_SYSTEM = 1 << 0; 59 private static final int PROPERTY_DYNAMIC = 1 << 1; 60 private static final int PROPERTY_LOADER = 1 << 2; 61 private static final int PROPERTY_OVERLAY = 1 << 3; 62 // END-INTERNAL 63 64 private static final String FRAMEWORK_APK_PATH = 65 ReflectionHelpers.getStaticField(AssetManager.class, "FRAMEWORK_APK_PATH"); 66 67 private static final HashMap<Key, WeakReference<ApkAssets>> cachedApkAssets = 68 new HashMap<>(); 69 private static final HashMap<Key, Long> cachedNativePtrs = new HashMap<>(); 70 71 @RealObject private ApkAssets realApkAssets; 72 getNativePtr()73 long getNativePtr() { 74 return ReflectionHelpers.getField(realApkAssets, "mNativePtr"); 75 } 76 77 /** 78 * Caching key for {@link ApkAssets}. 79 */ 80 private static class Key { 81 private final FileDescriptor fd; 82 private final String path; 83 private final boolean system; 84 private final boolean load_as_shared_library; 85 private final boolean overlay; 86 Key(FileDescriptor fd, String path, boolean system, boolean load_as_shared_library, boolean overlay)87 public Key(FileDescriptor fd, String path, boolean system, boolean load_as_shared_library, 88 boolean overlay) { 89 this.fd = fd; 90 this.path = path; 91 this.system = system; 92 this.load_as_shared_library = load_as_shared_library; 93 this.overlay = overlay; 94 } 95 96 @Override equals(Object o)97 public boolean equals(Object o) { 98 if (this == o) { 99 return true; 100 } 101 if (o == null || getClass() != o.getClass()) { 102 return false; 103 } 104 Key key = (Key) o; 105 return system == key.system && 106 load_as_shared_library == key.load_as_shared_library && 107 overlay == key.overlay && 108 Objects.equals(fd, key.fd) && 109 Objects.equals(path, key.path); 110 } 111 112 @Override hashCode()113 public int hashCode() { 114 return Objects.hash(fd, path, system, load_as_shared_library, overlay); 115 } 116 } 117 118 @FunctionalInterface 119 private interface ApkAssetMaker { call()120 ApkAssets call(); 121 } 122 123 // BEGIN-INTERNAL 124 // Overwrite ApkAssets.java to prevent clearing out the apk assets 125 @Implementation(minSdk = Build.VERSION_CODES.S) close()126 protected void close() { 127 } 128 // END-INTERNAL 129 getFromCacheOrLoad(Key key, ApkAssetMaker callable)130 private static ApkAssets getFromCacheOrLoad(Key key, ApkAssetMaker callable) { 131 synchronized (cachedApkAssets) { 132 WeakReference<ApkAssets> cachedRef = cachedApkAssets.get(key); 133 ApkAssets apkAssets; 134 if (cachedRef != null) { 135 apkAssets = cachedRef.get(); 136 if (apkAssets != null) { 137 return apkAssets; 138 } else { 139 cachedApkAssets.remove(key); 140 long nativePtr = cachedNativePtrs.remove(key); 141 Registries.NATIVE_APK_ASSETS_REGISTRY.unregister(nativePtr); 142 } 143 } 144 145 apkAssets = callable.call(); 146 long nativePtr = ((ShadowArscApkAssets9) Shadow.extract(apkAssets)).getNativePtr(); 147 cachedNativePtrs.put(key, nativePtr); 148 cachedApkAssets.put(key, new WeakReference<>(apkAssets)); 149 return apkAssets; 150 } 151 } 152 153 @Implementation loadFromPath(@onNull String path)154 protected static ApkAssets loadFromPath(@NonNull String path) throws IOException { 155 return getFromCacheOrLoad( 156 new Key(null, path, false, false, false), 157 () -> directlyOn(ApkAssets.class, "loadFromPath", ClassParameter.from(String.class, path))); 158 } 159 160 /** 161 * Necessary to shadow this method because the framework path is hard-coded. 162 * Called from AssetManager.createSystemAssetsInZygoteLocked() in P+. 163 */ 164 @Implementation loadFromPath(String path, boolean system)165 protected static ApkAssets loadFromPath(String path, boolean system) 166 throws IOException { 167 System.out.println( 168 "Called loadFromPath(" 169 + path 170 + ", " + system + "); mode=" 171 + (RuntimeEnvironment.useLegacyResources() ? "legacy" : "binary") 172 + " sdk=" + RuntimeEnvironment.getApiLevel()); 173 174 if (FRAMEWORK_APK_PATH.equals(path)) { 175 path = RuntimeEnvironment.getAndroidFrameworkJarPath(); 176 } 177 178 String finalPath = path; 179 return getFromCacheOrLoad( 180 new Key(null, path, system, false, false), 181 () -> directlyOn(ApkAssets.class, "loadFromPath", 182 ClassParameter.from(String.class, finalPath), 183 ClassParameter.from(boolean.class, system))); 184 } 185 186 // BEGIN-INTERNAL 187 @Implementation(minSdk = Build.VERSION_CODES.R) loadFromPath(String path, int flags)188 protected static ApkAssets loadFromPath(String path, int flags) 189 throws IOException { 190 boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM; 191 192 if (FRAMEWORK_APK_PATH.equals(path)) { 193 path = RuntimeEnvironment.getAndroidFrameworkJarPath(); 194 } 195 196 String finalPath = path; 197 return getFromCacheOrLoad( 198 new Key(null, path, system, false, false), 199 () -> directlyOn(ApkAssets.class, "loadFromPath", 200 ClassParameter.from(String.class, finalPath), 201 ClassParameter.from(int.class, flags))); 202 } 203 // END-INTERNAL 204 205 @Implementation loadFromPath(@onNull String path, boolean system, boolean forceSharedLibrary)206 protected static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, 207 boolean forceSharedLibrary) throws IOException { 208 return getFromCacheOrLoad( 209 new Key(null, path, system, forceSharedLibrary, false), 210 () -> directlyOn(ApkAssets.class, "loadFromPath", 211 ClassParameter.from(String.class, path), 212 ClassParameter.from(boolean.class, system), 213 ClassParameter.from(boolean.class, forceSharedLibrary))); 214 } 215 216 // BEGIN-INTERNAL 217 @Implementation(minSdk = Build.VERSION_CODES.R) loadFromPath(@onNull String path, int flags, AssetsProvider assets)218 protected static @NonNull ApkAssets loadFromPath(@NonNull String path, int flags, 219 AssetsProvider assets) throws IOException { 220 boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM; 221 boolean forceSharedLibrary = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC; 222 return getFromCacheOrLoad( 223 new Key(null, path, system, forceSharedLibrary, false), 224 () -> directlyOn(ApkAssets.class, "loadFromPath", 225 ClassParameter.from(String.class, path), 226 ClassParameter.from(int.class, flags), 227 ClassParameter.from(AssetsProvider.class, assets))); 228 } 229 // END-INTERNAL 230 231 @Implementation loadFromFd(FileDescriptor fd, String friendlyName, boolean system, boolean forceSharedLibrary)232 protected static ApkAssets loadFromFd(FileDescriptor fd, 233 String friendlyName, boolean system, boolean forceSharedLibrary) 234 throws IOException { 235 return getFromCacheOrLoad( 236 new Key(fd, friendlyName, system, forceSharedLibrary, false), 237 () -> directlyOn(ApkAssets.class, "loadFromFd", 238 ClassParameter.from(FileDescriptor.class, fd), 239 ClassParameter.from(String.class, friendlyName), 240 ClassParameter.from(boolean.class, system), 241 ClassParameter.from(boolean.class, forceSharedLibrary))); 242 } 243 244 // BEGIN-INTERNAL 245 @Implementation(minSdk = Build.VERSION_CODES.R) loadFromFd(FileDescriptor fd, String friendlyName, int flags, AssetsProvider assets)246 protected static ApkAssets loadFromFd(FileDescriptor fd, 247 String friendlyName, int flags, AssetsProvider assets) 248 throws IOException { 249 boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM; 250 boolean forceSharedLibrary = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC; 251 return getFromCacheOrLoad( 252 new Key(fd, friendlyName, system, forceSharedLibrary, false), 253 () -> directlyOn(ApkAssets.class, "loadFromFd", 254 ClassParameter.from(FileDescriptor.class, fd), 255 ClassParameter.from(String.class, friendlyName), 256 ClassParameter.from(int.class, flags), 257 ClassParameter.from(AssetsProvider.class, assets))); 258 } 259 // END-INTERNAL 260 261 @Implementation loadOverlayFromPath(@onNull String idmapPath, boolean system)262 protected static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) 263 throws IOException { 264 throw new UnsupportedOperationException(); 265 // return getFromCacheOrLoad( 266 // new Key(fd, friendlyName, system, forceSharedLibrary, false), 267 // () -> directlyOn(ApkAssets.class, "loadFromPath", 268 // ClassParameter.from(FileDescriptor.class, fd), 269 // ClassParameter.from(String.class, friendlyName), 270 // ClassParameter.from(boolean.class, system), 271 // ClassParameter.from(boolean.class, forceSharedLibrary))); 272 } 273 274 // static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system, 275 // jboolean force_shared_lib, jboolean overlay) { 276 @Implementation(maxSdk = Build.VERSION_CODES.Q) nativeLoad(String java_path, boolean system, boolean force_shared_lib, boolean overlay)277 protected static long nativeLoad(String java_path, boolean system, 278 boolean force_shared_lib, boolean overlay) throws IOException { 279 String path = java_path; 280 if (path == null) { 281 return 0; 282 } 283 284 ATRACE_NAME(String.format("LoadApkAssets(%s)", path)); 285 286 CppApkAssets apk_assets; 287 try { 288 if (overlay) { 289 apk_assets = CppApkAssets.LoadOverlay(path, system); 290 } else if (force_shared_lib) { 291 apk_assets = 292 CppApkAssets.LoadAsSharedLibrary(path, system); 293 } else { 294 apk_assets = CppApkAssets.Load(path, system); 295 } 296 } catch (OutOfMemoryError e) { 297 OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path); 298 outOfMemoryError.initCause(e); 299 throw outOfMemoryError; 300 } 301 302 if (apk_assets == null) { 303 String error_msg = String.format("Failed to load asset path %s", path); 304 throw new IOException(error_msg); 305 } 306 return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets); 307 } 308 309 // BEGIN-INTERNAL 310 // static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format, 311 // jstring java_path, const jint property_flags, jobject assets_provider) 312 @Implementation(minSdk = Build.VERSION_CODES.R) nativeLoad(int format, String java_path, int flags, AssetsProvider asset)313 protected static long nativeLoad(int format, String java_path, 314 int flags, AssetsProvider asset) throws IOException { 315 String path = java_path; 316 if (path == null) { 317 return 0; 318 } 319 320 ATRACE_NAME(String.format("LoadApkAssets(%s)", path)); 321 322 boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM; 323 boolean overlay = (flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY; 324 boolean force_shared_lib = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC; 325 CppApkAssets apk_assets; 326 try { 327 if (overlay) { 328 apk_assets = CppApkAssets.LoadOverlay(path, system); 329 } else if (force_shared_lib) { 330 apk_assets = 331 CppApkAssets.LoadAsSharedLibrary(path, system); 332 } else { 333 apk_assets = CppApkAssets.Load(path, system); 334 } 335 } catch (OutOfMemoryError e) { 336 OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path); 337 outOfMemoryError.initCause(e); 338 throw outOfMemoryError; 339 } 340 341 if (apk_assets == null) { 342 String error_msg = String.format("Failed to load asset path %s", path); 343 throw new IOException(error_msg); 344 } 345 return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets); 346 } 347 // END-INTERNAL 348 349 // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor, 350 // jstring friendly_name, jboolean system, jboolean 351 // force_shared_lib) { 352 @Implementation(maxSdk = Build.VERSION_CODES.Q) nativeLoadFromFd(FileDescriptor file_descriptor, String friendly_name, boolean system, boolean force_shared_lib)353 protected static long nativeLoadFromFd(FileDescriptor file_descriptor, 354 String friendly_name, boolean system, boolean force_shared_lib) { 355 String friendly_name_utf8 = friendly_name; 356 if (friendly_name_utf8 == null) { 357 return 0; 358 } 359 360 throw new UnsupportedOperationException(); 361 // ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8)); 362 // 363 // int fd = jniGetFDFromFileDescriptor(env, file_descriptor); 364 // if (fd < 0) { 365 // throw new IllegalArgumentException("Bad FileDescriptor"); 366 // } 367 // 368 // unique_fd dup_fd(.dup(fd)); 369 // if (dup_fd < 0) { 370 // throw new IOException(errno); 371 // return 0; 372 // } 373 // 374 // ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd), 375 // friendly_name_utf8, 376 // system, force_shared_lib); 377 // if (apk_assets == null) { 378 // String error_msg = String.format("Failed to load asset path %s from fd %d", 379 // friendly_name_utf8, dup_fd.get()); 380 // throw new IOException(error_msg); 381 // return 0; 382 // } 383 // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets); 384 } 385 386 // BEGIN-INTERNAL 387 // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format, 388 // jobject file_descriptor, jstring friendly_name, 389 // const jint property_flags, jobject assets_provider) { 390 @Implementation(minSdk = Build.VERSION_CODES.R) nativeLoadFromFd(int format, FileDescriptor fd, String friendlyName, int flags, AssetsProvider asset)391 protected static long nativeLoadFromFd(int format, FileDescriptor fd, 392 String friendlyName, int flags, AssetsProvider asset) { 393 String friendly_name_utf8 = friendlyName; 394 if (friendly_name_utf8 == null) { 395 return 0; 396 } 397 398 throw new UnsupportedOperationException(); 399 // ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8)); 400 // 401 // int fd = jniGetFDFromFileDescriptor(env, file_descriptor); 402 // if (fd < 0) { 403 // throw new IllegalArgumentException("Bad FileDescriptor"); 404 // } 405 // 406 // unique_fd dup_fd(.dup(fd)); 407 // if (dup_fd < 0) { 408 // throw new IOException(errno); 409 // return 0; 410 // } 411 // 412 // ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd), 413 // friendly_name_utf8, 414 // system, force_shared_lib); 415 // if (apk_assets == null) { 416 // String error_msg = String.format("Failed to load asset path %s from fd %d", 417 // friendly_name_utf8, dup_fd.get()); 418 // throw new IOException(error_msg); 419 // return 0; 420 // } 421 // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets); 422 } 423 // END-INTERNAL 424 425 // static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) { 426 @Implementation nativeGetAssetPath(long ptr)427 protected static String nativeGetAssetPath(long ptr) { 428 CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr); 429 return apk_assets.GetPath(); 430 } 431 432 // static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { 433 @Implementation nativeGetStringBlock(long ptr)434 protected static long nativeGetStringBlock(long ptr) { 435 CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr); 436 return apk_assets.GetLoadedArsc().GetStringPool().getNativePtr(); 437 } 438 439 // static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { 440 @Implementation nativeIsUpToDate(long ptr)441 protected static boolean nativeIsUpToDate(long ptr) { 442 CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr); 443 // (void)apk_assets; 444 return JNI_TRUE; 445 } 446 447 // static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { 448 @Implementation nativeOpenXml(long ptr, String file_name)449 protected static long nativeOpenXml(long ptr, String file_name) throws FileNotFoundException { 450 String path_utf8 = file_name; 451 if (path_utf8 == null) { 452 return 0; 453 } 454 455 CppApkAssets apk_assets = 456 Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr); 457 Asset asset = apk_assets.Open(path_utf8, 458 Asset.AccessMode.ACCESS_RANDOM); 459 if (asset == null) { 460 throw new FileNotFoundException(path_utf8); 461 } 462 463 // DynamicRefTable is only needed when looking up resource references. Opening an XML file 464 // directly from an ApkAssets has no notion of proper resource references. 465 ResXMLTree xml_tree = new ResXMLTree(null); // util.make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/); 466 int err = xml_tree.setTo(asset.getBuffer(true), (int) asset.getLength(), true); 467 // asset.reset(); 468 469 if (err != NO_ERROR) { 470 throw new FileNotFoundException("Corrupt XML binary file"); 471 } 472 return Registries.NATIVE_RES_XML_TREES.register(xml_tree); // reinterpret_cast<jlong>(xml_tree.release()); 473 } 474 475 // // JNI registration. 476 // static const JNINativeMethod gApkAssetsMethods[] = { 477 // {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad}, 478 // {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J", 479 // (void*)NativeLoadFromFd}, 480 // {"nativeDestroy", "(J)V", (void*)NativeDestroy}, 481 // {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, 482 // {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, 483 // {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, 484 // {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, 485 // }; 486 // 487 // int register_android_content_res_ApkAssets(JNIEnv* env) { 488 // return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods, 489 // arraysize(gApkAssetsMethods)); 490 // } 491 // 492 // } // namespace android 493 } 494