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