1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.content.res; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.om.OverlayableInfo; 23 import android.content.res.loader.AssetsProvider; 24 import android.content.res.loader.ResourcesProvider; 25 import android.text.TextUtils; 26 27 import com.android.internal.annotations.GuardedBy; 28 29 import dalvik.annotation.optimization.CriticalNative; 30 31 import java.io.FileDescriptor; 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.lang.annotation.Retention; 35 import java.lang.annotation.RetentionPolicy; 36 import java.util.Objects; 37 38 /** 39 * The loaded, immutable, in-memory representation of an APK. 40 * 41 * The main implementation is native C++ and there is very little API surface exposed here. The APK 42 * is mainly accessed via {@link AssetManager}. 43 * 44 * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers, 45 * making the creation of AssetManagers very cheap. 46 * @hide 47 */ 48 public final class ApkAssets { 49 50 /** 51 * The apk assets contains framework resource values specified by the system. 52 * This allows some functions to filter out this package when computing what 53 * configurations/resources are available. 54 */ 55 public static final int PROPERTY_SYSTEM = 1 << 0; 56 57 /** 58 * The apk assets is a shared library or was loaded as a shared library by force. 59 * The package ids of dynamic apk assets are assigned at runtime instead of compile time. 60 */ 61 public static final int PROPERTY_DYNAMIC = 1 << 1; 62 63 /** 64 * The apk assets has been loaded dynamically using a {@link ResourcesProvider}. 65 * Loader apk assets overlay resources like RROs except they are not backed by an idmap. 66 */ 67 public static final int PROPERTY_LOADER = 1 << 2; 68 69 /** 70 * The apk assets is a RRO. 71 * An RRO overlays resource values of its target package. 72 */ 73 private static final int PROPERTY_OVERLAY = 1 << 3; 74 75 /** 76 * The apk assets is owned by the application running in this process and incremental crash 77 * protections for this APK must be disabled. 78 */ 79 public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4; 80 81 /** Flags that change the behavior of loaded apk assets. */ 82 @IntDef(prefix = { "PROPERTY_" }, value = { 83 PROPERTY_SYSTEM, 84 PROPERTY_DYNAMIC, 85 PROPERTY_LOADER, 86 PROPERTY_OVERLAY, 87 }) 88 @Retention(RetentionPolicy.SOURCE) 89 public @interface PropertyFlags {} 90 91 /** The path used to load the apk assets represents an APK file. */ 92 private static final int FORMAT_APK = 0; 93 94 /** The path used to load the apk assets represents an idmap file. */ 95 private static final int FORMAT_IDMAP = 1; 96 97 /** The path used to load the apk assets represents an resources.arsc file. */ 98 private static final int FORMAT_ARSC = 2; 99 100 /** the path used to load the apk assets represents a directory. */ 101 private static final int FORMAT_DIR = 3; 102 103 // Format types that change how the apk assets are loaded. 104 @IntDef(prefix = { "FORMAT_" }, value = { 105 FORMAT_APK, 106 FORMAT_IDMAP, 107 FORMAT_ARSC, 108 FORMAT_DIR 109 }) 110 @Retention(RetentionPolicy.SOURCE) 111 public @interface FormatType {} 112 113 @GuardedBy("this") 114 private long mNativePtr; // final, except cleared in finalizer. 115 116 @Nullable 117 @GuardedBy("this") 118 private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. 119 120 @PropertyFlags 121 private final int mFlags; 122 123 @Nullable 124 private final AssetsProvider mAssets; 125 126 /** 127 * Creates a new ApkAssets instance from the given path on disk. 128 * 129 * @param path The path to an APK on disk. 130 * @return a new instance of ApkAssets. 131 * @throws IOException if a disk I/O error or parsing error occurred. 132 */ loadFromPath(@onNull String path)133 public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { 134 return loadFromPath(path, 0 /* flags */); 135 } 136 137 /** 138 * Creates a new ApkAssets instance from the given path on disk. 139 * 140 * @param path The path to an APK on disk. 141 * @param flags flags that change the behavior of loaded apk assets 142 * @return a new instance of ApkAssets. 143 * @throws IOException if a disk I/O error or parsing error occurred. 144 */ loadFromPath(@onNull String path, @PropertyFlags int flags)145 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags) 146 throws IOException { 147 return new ApkAssets(FORMAT_APK, path, flags, null /* assets */); 148 } 149 150 /** 151 * Creates a new ApkAssets instance from the given path on disk. 152 * 153 * @param path The path to an APK on disk. 154 * @param flags flags that change the behavior of loaded apk assets 155 * @param assets The assets provider that overrides the loading of file-based resources 156 * @return a new instance of ApkAssets. 157 * @throws IOException if a disk I/O error or parsing error occurred. 158 */ loadFromPath(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)159 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags, 160 @Nullable AssetsProvider assets) throws IOException { 161 return new ApkAssets(FORMAT_APK, path, flags, assets); 162 } 163 164 /** 165 * Creates a new ApkAssets instance from the given file descriptor. 166 * 167 * Performs a dup of the underlying fd, so you must take care of still closing 168 * the FileDescriptor yourself (and can do that whenever you want). 169 * 170 * @param fd The FileDescriptor of an open, readable APK. 171 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 172 * @param flags flags that change the behavior of loaded apk assets 173 * @param assets The assets provider that overrides the loading of file-based resources 174 * @return a new instance of ApkAssets. 175 * @throws IOException if a disk I/O error or parsing error occurred. 176 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)177 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 178 @NonNull String friendlyName, @PropertyFlags int flags, 179 @Nullable AssetsProvider assets) throws IOException { 180 return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets); 181 } 182 183 /** 184 * Creates a new ApkAssets instance from the given file descriptor. 185 * 186 * Performs a dup of the underlying fd, so you must take care of still closing 187 * the FileDescriptor yourself (and can do that whenever you want). 188 * 189 * @param fd The FileDescriptor of an open, readable APK. 190 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 191 * @param offset The location within the file that the apk starts. This must be 0 if length is 192 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 193 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 194 * if it extends to the end of the file. 195 * @param flags flags that change the behavior of loaded apk assets 196 * @param assets The assets provider that overrides the loading of file-based resources 197 * @return a new instance of ApkAssets. 198 * @throws IOException if a disk I/O error or parsing error occurred. 199 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)200 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 201 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 202 @Nullable AssetsProvider assets) 203 throws IOException { 204 return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); 205 } 206 207 /** 208 * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path 209 * is encoded within the IDMAP. 210 * 211 * @param idmapPath Path to the IDMAP of an overlay APK. 212 * @param flags flags that change the behavior of loaded apk assets 213 * @return a new instance of ApkAssets. 214 * @throws IOException if a disk I/O error or parsing error occurred. 215 */ loadOverlayFromPath(@onNull String idmapPath, @PropertyFlags int flags)216 public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, 217 @PropertyFlags int flags) throws IOException { 218 return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */); 219 } 220 221 /** 222 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 223 * for use with a {@link ResourcesProvider}. 224 * 225 * Performs a dup of the underlying fd, so you must take care of still closing 226 * the FileDescriptor yourself (and can do that whenever you want). 227 * 228 * @param fd The FileDescriptor of an open, readable resources.arsc. 229 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 230 * @param flags flags that change the behavior of loaded apk assets 231 * @param assets The assets provider that overrides the loading of file-based resources 232 * @return a new instance of ApkAssets. 233 * @throws IOException if a disk I/O error or parsing error occurred. 234 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)235 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 236 @NonNull String friendlyName, @PropertyFlags int flags, 237 @Nullable AssetsProvider assets) throws IOException { 238 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets); 239 } 240 241 /** 242 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 243 * for use with a {@link ResourcesProvider}. 244 * 245 * Performs a dup of the underlying fd, so you must take care of still closing 246 * the FileDescriptor yourself (and can do that whenever you want). 247 * 248 * @param fd The FileDescriptor of an open, readable resources.arsc. 249 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 250 * @param offset The location within the file that the table starts. This must be 0 if length is 251 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 252 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 253 * if it extends to the end of the file. 254 * @param flags flags that change the behavior of loaded apk assets 255 * @param assets The assets provider that overrides the loading of file-based resources 256 * @return a new instance of ApkAssets. 257 * @throws IOException if a disk I/O error or parsing error occurred. 258 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)259 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 260 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 261 @Nullable AssetsProvider assets) throws IOException { 262 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets); 263 } 264 265 /** 266 * Creates a new ApkAssets instance from the given directory path. The directory should have the 267 * file structure of an APK. 268 * 269 * @param path The path to a directory on disk. 270 * @param flags flags that change the behavior of loaded apk assets 271 * @param assets The assets provider that overrides the loading of file-based resources 272 * @return a new instance of ApkAssets. 273 * @throws IOException if a disk I/O error or parsing error occurred. 274 */ loadFromDir(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)275 public static @NonNull ApkAssets loadFromDir(@NonNull String path, 276 @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { 277 return new ApkAssets(FORMAT_DIR, path, flags, assets); 278 } 279 280 /** 281 * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence 282 * is required for a lot of APIs, and it's easier to have a non-null reference rather than 283 * tracking a separate identifier. 284 * 285 * @param flags flags that change the behavior of loaded apk assets 286 * @param assets The assets provider that overrides the loading of file-based resources 287 */ 288 @NonNull loadEmptyForLoader(@ropertyFlags int flags, @Nullable AssetsProvider assets)289 public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, 290 @Nullable AssetsProvider assets) { 291 return new ApkAssets(flags, assets); 292 } 293 ApkAssets(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)294 private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, 295 @Nullable AssetsProvider assets) throws IOException { 296 Objects.requireNonNull(path, "path"); 297 mFlags = flags; 298 mNativePtr = nativeLoad(format, path, flags, assets); 299 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 300 mAssets = assets; 301 } 302 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)303 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 304 @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) 305 throws IOException { 306 Objects.requireNonNull(fd, "fd"); 307 Objects.requireNonNull(friendlyName, "friendlyName"); 308 mFlags = flags; 309 mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); 310 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 311 mAssets = assets; 312 } 313 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)314 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 315 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 316 @Nullable AssetsProvider assets) throws IOException { 317 Objects.requireNonNull(fd, "fd"); 318 Objects.requireNonNull(friendlyName, "friendlyName"); 319 mFlags = flags; 320 mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); 321 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 322 mAssets = assets; 323 } 324 ApkAssets(@ropertyFlags int flags, @Nullable AssetsProvider assets)325 private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { 326 mFlags = flags; 327 mNativePtr = nativeLoadEmpty(flags, assets); 328 mStringBlock = null; 329 mAssets = assets; 330 } 331 332 @UnsupportedAppUsage getAssetPath()333 public @NonNull String getAssetPath() { 334 synchronized (this) { 335 return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr)); 336 } 337 } 338 339 /** @hide */ getDebugName()340 public @NonNull String getDebugName() { 341 synchronized (this) { 342 return nativeGetDebugName(mNativePtr); 343 } 344 } 345 346 @Nullable getStringFromPool(int idx)347 CharSequence getStringFromPool(int idx) { 348 if (mStringBlock == null) { 349 return null; 350 } 351 352 synchronized (this) { 353 return mStringBlock.getSequence(idx); 354 } 355 } 356 357 /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */ isForLoader()358 public boolean isForLoader() { 359 return (mFlags & PROPERTY_LOADER) != 0; 360 } 361 362 /** 363 * Returns the assets provider that overrides the loading of assets present in this apk assets. 364 */ 365 @Nullable getAssetsProvider()366 public AssetsProvider getAssetsProvider() { 367 return mAssets; 368 } 369 370 /** 371 * Retrieve a parser for a compiled XML file. This is associated with a single APK and 372 * <em>NOT</em> a full AssetManager. This means that shared-library references will not be 373 * dynamically assigned runtime package IDs. 374 * 375 * @param fileName The path to the file within the APK. 376 * @return An XmlResourceParser. 377 * @throws IOException if the file was not found or an error occurred retrieving it. 378 */ openXml(@onNull String fileName)379 public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { 380 Objects.requireNonNull(fileName, "fileName"); 381 synchronized (this) { 382 long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); 383 try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { 384 XmlResourceParser parser = block.newParser(); 385 // If nativeOpenXml doesn't throw, it will always return a valid native pointer, 386 // which makes newParser always return non-null. But let's be careful. 387 if (parser == null) { 388 throw new AssertionError("block.newParser() returned a null parser"); 389 } 390 return parser; 391 } 392 } 393 } 394 395 /** @hide */ 396 @Nullable getOverlayableInfo(String overlayableName)397 public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException { 398 synchronized (this) { 399 return nativeGetOverlayableInfo(mNativePtr, overlayableName); 400 } 401 } 402 403 /** @hide */ definesOverlayable()404 public boolean definesOverlayable() throws IOException { 405 synchronized (this) { 406 return nativeDefinesOverlayable(mNativePtr); 407 } 408 } 409 410 /** 411 * Returns false if the underlying APK was changed since this ApkAssets was loaded. 412 */ isUpToDate()413 public boolean isUpToDate() { 414 synchronized (this) { 415 return nativeIsUpToDate(mNativePtr); 416 } 417 } 418 419 @Override toString()420 public String toString() { 421 return "ApkAssets{path=" + getDebugName() + "}"; 422 } 423 424 @Override finalize()425 protected void finalize() throws Throwable { 426 close(); 427 } 428 429 /** 430 * Closes this class and the contained {@link #mStringBlock}. 431 */ close()432 public void close() { 433 synchronized (this) { 434 if (mNativePtr != 0) { 435 if (mStringBlock != null) { 436 mStringBlock.close(); 437 } 438 nativeDestroy(mNativePtr); 439 mNativePtr = 0; 440 } 441 } 442 } 443 dump(PrintWriter pw, String prefix)444 void dump(PrintWriter pw, String prefix) { 445 pw.println(prefix + "class=" + getClass()); 446 pw.println(prefix + "debugName=" + getDebugName()); 447 pw.println(prefix + "assetPath=" + getAssetPath()); 448 } 449 nativeLoad(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset)450 private static native long nativeLoad(@FormatType int format, @NonNull String path, 451 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeLoadEmpty(@ropertyFlags int flags, @Nullable AssetsProvider asset)452 private static native long nativeLoadEmpty(@PropertyFlags int flags, 453 @Nullable AssetsProvider asset); nativeLoadFd(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider asset)454 private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, 455 @NonNull String friendlyName, @PropertyFlags int flags, 456 @Nullable AssetsProvider asset) throws IOException; nativeLoadFdOffsets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider asset)457 private static native long nativeLoadFdOffsets(@FormatType int format, 458 @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, 459 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeDestroy(long ptr)460 private static native void nativeDestroy(long ptr); nativeGetAssetPath(long ptr)461 private static native @NonNull String nativeGetAssetPath(long ptr); nativeGetDebugName(long ptr)462 private static native @NonNull String nativeGetDebugName(long ptr); nativeGetStringBlock(long ptr)463 private static native long nativeGetStringBlock(long ptr); nativeIsUpToDate(long ptr)464 @CriticalNative private static native boolean nativeIsUpToDate(long ptr); nativeOpenXml(long ptr, @NonNull String fileName)465 private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; nativeGetOverlayableInfo(long ptr, String overlayableName)466 private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, 467 String overlayableName) throws IOException; nativeDefinesOverlayable(long ptr)468 private static native boolean nativeDefinesOverlayable(long ptr) throws IOException; 469 } 470