• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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