• 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.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