• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.P;
4 import static android.os.Build.VERSION_CODES.Q;
5 import static android.os.Build.VERSION_CODES.R;
6 import static org.robolectric.res.android.Errors.NO_ERROR;
7 import static org.robolectric.res.android.Util.ATRACE_NAME;
8 import static org.robolectric.util.reflector.Reflector.reflector;
9 
10 import android.content.res.ApkAssets;
11 import android.content.res.AssetManager;
12 import java.io.FileDescriptor;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import org.robolectric.RuntimeEnvironment;
16 import org.robolectric.annotation.ClassName;
17 import org.robolectric.annotation.Implementation;
18 import org.robolectric.annotation.Implements;
19 import org.robolectric.annotation.RealObject;
20 import org.robolectric.res.android.ApkAssetsCache;
21 import org.robolectric.res.android.Asset;
22 import org.robolectric.res.android.CppApkAssets;
23 import org.robolectric.res.android.Registries;
24 import org.robolectric.res.android.ResXMLTree;
25 import org.robolectric.shadows.ShadowApkAssets.Picker;
26 import org.robolectric.util.PerfStatsCollector;
27 import org.robolectric.util.ReflectionHelpers;
28 import org.robolectric.util.reflector.Accessor;
29 import org.robolectric.util.reflector.Direct;
30 import org.robolectric.util.reflector.ForType;
31 import org.robolectric.util.reflector.Static;
32 
33 // transliterated from
34 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_content_res_ApkAssets.cpp
35 
36 /** Shadow for {@link ApkAssets} for Android P+ */
37 @Implements(
38     value = ApkAssets.class,
39     minSdk = P,
40     shadowPicker = Picker.class,
41     isInAndroidSdk = false)
42 public class ShadowArscApkAssets9 extends ShadowApkAssets {
43   // #define ATRACE_TAG ATRACE_TAG_RESOURCES
44   //
45   // #include "android-base/macros.h"
46   // #include "android-base/stringprintf.h"
47   // #include "android-base/unique_fd.h"
48   // #include "androidfw/ApkAssets.h"
49   // #include "utils/misc.h"
50   // #include "utils/Trace.h"
51   //
52   // #include "core_jni_helpers.h"
53   // #include "jni.h"
54   // #include "nativehelper/ScopedUtfChars.h"
55   //
56   // using ::android::base::unique_fd;
57   //
58   // namespace android {
59 
60   // TODO: just use the ApkAssets constants. For some unknown reason these cannot be found
61   private static final int PROPERTY_SYSTEM = 1 << 0;
62   private static final int PROPERTY_DYNAMIC = 1 << 1;
63   private static final int PROPERTY_OVERLAY = 1 << 3;
64 
65   protected static final String FRAMEWORK_APK_PATH =
66       ReflectionHelpers.getStaticField(AssetManager.class, "FRAMEWORK_APK_PATH");
67 
68   @RealObject private ApkAssets realApkAssets;
69 
getNativePtr()70   long getNativePtr() {
71     return reflector(_ApkAssets_.class, realApkAssets).getNativePtr();
72   }
73 
74   /** Reflector interface for {@link ApkAssets}'s internals. */
75   @ForType(ApkAssets.class)
76   interface _ApkAssets_ {
77     @Static
78     @Direct
loadFromPath(String finalPath, boolean system)79     ApkAssets loadFromPath(String finalPath, boolean system);
80 
81     @Static
82     @Direct
loadFromPath(String finalPath, int flags)83     ApkAssets loadFromPath(String finalPath, int flags);
84 
85     @Accessor("mNativePtr")
getNativePtr()86     long getNativePtr();
87   }
88 
89   /**
90    * Necessary to shadow this method because the framework path is hard-coded. Called from
91    * AssetManager.createSystemAssetsInZygoteLocked() in P+.
92    */
93   @Implementation(maxSdk = Q)
loadFromPath(String path, boolean system)94   protected static ApkAssets loadFromPath(String path, boolean system) throws IOException {
95     if (FRAMEWORK_APK_PATH.equals(path)) {
96       path = RuntimeEnvironment.getAndroidFrameworkJarPath().toString();
97     }
98 
99     return reflector(_ApkAssets_.class).loadFromPath(path, system);
100   }
101 
102   @Implementation(minSdk = R)
loadFromPath(String path, int flags)103   protected static ApkAssets loadFromPath(String path, int flags) throws IOException {
104     if (FRAMEWORK_APK_PATH.equals(path)) {
105       path = RuntimeEnvironment.getAndroidFrameworkJarPath().toString();
106     }
107     return reflector(_ApkAssets_.class).loadFromPath(path, flags);
108   }
109 
110   // static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
111   //                         jboolean force_shared_lib, jboolean overlay) {
112 
113   @Implementation(maxSdk = Q)
nativeLoad( String path, boolean system, boolean forceSharedLib, boolean overlay)114   protected static long nativeLoad(
115       String path, boolean system, boolean forceSharedLib, boolean overlay) throws IOException {
116     if (path == null) {
117       return 0;
118     }
119 
120     long cachedApkAssetsPtr = ApkAssetsCache.get(path, system, RuntimeEnvironment.getApiLevel());
121     if (cachedApkAssetsPtr != -1) {
122       return cachedApkAssetsPtr;
123     }
124 
125     return PerfStatsCollector.getInstance()
126         .measure(
127             "load binary " + (system ? "framework" : "app") + " resources",
128             () -> {
129               ATRACE_NAME(String.format("LoadApkAssets(%s)", path));
130 
131               CppApkAssets apk_assets;
132               try {
133                 if (overlay) {
134                   apk_assets = CppApkAssets.LoadOverlay(path, system);
135                 } else if (forceSharedLib) {
136                   apk_assets = CppApkAssets.LoadAsSharedLibrary(path, system);
137                 } else {
138                   apk_assets = CppApkAssets.Load(path, system);
139                 }
140               } catch (OutOfMemoryError e) {
141                 OutOfMemoryError outOfMemoryError = new OutOfMemoryError("Failed to load " + path);
142                 outOfMemoryError.initCause(e);
143                 throw outOfMemoryError;
144               }
145 
146               if (apk_assets == null) {
147                 String error_msg = String.format("Failed to load asset path %s", path);
148                 throw new IOException(error_msg);
149               }
150               long ptr = Registries.NATIVE_APK_ASSETS_REGISTRY.register(apk_assets);
151               ApkAssetsCache.put(path, system, RuntimeEnvironment.getApiLevel(), ptr);
152               return ptr;
153             });
154   }
155 
156   @Implementation(minSdk = R)
nativeLoad( int format, String javaPath, int flags, @ClassName("android.content.res.loader.AssetsProvider") Object assetsProvider)157   protected static long nativeLoad(
158       int format,
159       String javaPath,
160       int flags,
161       @ClassName("android.content.res.loader.AssetsProvider") Object assetsProvider)
162       throws IOException {
163     boolean system = (flags & PROPERTY_SYSTEM) == PROPERTY_SYSTEM;
164     boolean overlay = (flags & PROPERTY_OVERLAY) == PROPERTY_OVERLAY;
165     boolean forceSharedLib = (flags & PROPERTY_DYNAMIC) == PROPERTY_DYNAMIC;
166     return nativeLoad(javaPath, system, forceSharedLib, overlay);
167   }
168 
169   // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
170   //                               jstring friendly_name, jboolean system, jboolean
171   // force_shared_lib) {
172   @Implementation(maxSdk = Q)
nativeLoadFromFd( FileDescriptor file_descriptor, String friendly_name, boolean system, boolean force_shared_lib)173   protected static long nativeLoadFromFd(
174       FileDescriptor file_descriptor,
175       String friendly_name,
176       boolean system,
177       boolean force_shared_lib) {
178     String friendly_name_utf8 = friendly_name;
179     if (friendly_name_utf8 == null) {
180       return 0;
181     }
182 
183     throw new UnsupportedOperationException();
184     // ATRACE_NAME(String.format("LoadApkAssetsFd(%s)", friendly_name_utf8));
185     //
186     // int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
187     // if (fd < 0) {
188     //   throw new IllegalArgumentException("Bad FileDescriptor");
189     // }
190     //
191     // unique_fd dup_fd(.dup(fd));
192     // if (dup_fd < 0) {
193     //   throw new IOException(errno);
194     //   return 0;
195     // }
196     //
197     // ApkAssets apk_assets = ApkAssets.LoadFromFd(std.move(dup_fd),
198     //                                                                     friendly_name_utf8,
199     //                                                                     system,
200     // force_shared_lib);
201     // if (apk_assets == null) {
202     //   String error_msg = String.format("Failed to load asset path %s from fd %d",
203     //                                              friendly_name_utf8, dup_fd.get());
204     //   throw new IOException(error_msg);
205     //   return 0;
206     // }
207     // return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
208   }
209 
210   // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
211   //                             jobject file_descriptor, jstring friendly_name,
212   //                             const jint property_flags, jobject assets_provider)
213   @Implementation(minSdk = R)
nativeLoadFd( int format, FileDescriptor fileDescriptor, String friendlyName, int propertyFlags, @ClassName("android.content.res.loader.AssetsProvider") Object assetsProvider)214   protected static long nativeLoadFd(
215       int format,
216       FileDescriptor fileDescriptor,
217       String friendlyName,
218       int propertyFlags,
219       @ClassName("android.content.res.loader.AssetsProvider") Object assetsProvider)
220       throws IOException {
221     CppApkAssets apkAssets = CppApkAssets.loadArscFromFd((FileDescriptor) fileDescriptor);
222     if (apkAssets == null) {
223       String errorMessage =
224           String.format("Failed to load from the file descriptor %s", fileDescriptor);
225       throw new IOException(errorMessage);
226     }
227     return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apkAssets);
228   }
229 
230   // static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
231   @Implementation
nativeGetAssetPath(long ptr)232   protected static String nativeGetAssetPath(long ptr) {
233     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
234     return apk_assets.GetPath();
235   }
236 
237   // static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
238   @Implementation
nativeGetStringBlock(long ptr)239   protected static long nativeGetStringBlock(long ptr) {
240     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
241     return apk_assets.GetLoadedArsc().GetStringPool().getNativePtr();
242   }
243 
244   @Implementation
isUpToDate()245   protected boolean isUpToDate() {
246     return true;
247   }
248 
249   // static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
250   @Implementation
nativeOpenXml(long ptr, String file_name)251   protected static long nativeOpenXml(long ptr, String file_name) throws FileNotFoundException {
252     String path_utf8 = file_name;
253     if (path_utf8 == null) {
254       return 0;
255     }
256 
257     CppApkAssets apk_assets = Registries.NATIVE_APK_ASSETS_REGISTRY.getNativeObject(ptr);
258     Asset asset = apk_assets.Open(path_utf8, Asset.AccessMode.ACCESS_RANDOM);
259     if (asset == null) {
260       throw new FileNotFoundException(path_utf8);
261     }
262 
263     // DynamicRefTable is only needed when looking up resource references. Opening an XML file
264     // directly from an ApkAssets has no notion of proper resource references.
265     ResXMLTree xml_tree =
266         new ResXMLTree(null); // util.make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
267     int err = xml_tree.setTo(asset.getBuffer(true), (int) asset.getLength(), true);
268     // asset.reset();
269 
270     if (err != NO_ERROR) {
271       throw new FileNotFoundException("Corrupt XML binary file");
272     }
273     return Registries.NATIVE_RES_XML_TREES.register(
274         xml_tree); // reinterpret_cast<jlong>(xml_tree.release());
275   }
276 
277   // // JNI registration.
278   // static const JNINativeMethod gApkAssetsMethods[] = {
279   //     {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
280   //     {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
281   //         (void*)NativeLoadFromFd},
282   //     {"nativeDestroy", "(J)V", (void*)NativeDestroy},
283   //     {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
284   //     {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
285   //     {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
286   //     {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
287   // };
288   //
289   // int register_android_content_res_ApkAssets(JNIEnv* env) {
290   //   return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
291   //                               arraysize(gApkAssetsMethods));
292   // }
293   //
294   // }  // namespace android
295 }
296