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 /** 82 * The apk assets only contain the overlayable declarations information. 83 */ 84 public static final int PROPERTY_ONLY_OVERLAYABLES = 1 << 5; 85 86 /** Flags that change the behavior of loaded apk assets. */ 87 @IntDef(prefix = { "PROPERTY_" }, value = { 88 PROPERTY_SYSTEM, 89 PROPERTY_DYNAMIC, 90 PROPERTY_LOADER, 91 PROPERTY_OVERLAY, 92 }) 93 @Retention(RetentionPolicy.SOURCE) 94 public @interface PropertyFlags {} 95 96 /** The path used to load the apk assets represents an APK file. */ 97 private static final int FORMAT_APK = 0; 98 99 /** The path used to load the apk assets represents an idmap file. */ 100 private static final int FORMAT_IDMAP = 1; 101 102 /** The path used to load the apk assets represents an resources.arsc file. */ 103 private static final int FORMAT_ARSC = 2; 104 105 /** the path used to load the apk assets represents a directory. */ 106 private static final int FORMAT_DIR = 3; 107 108 // Format types that change how the apk assets are loaded. 109 @IntDef(prefix = { "FORMAT_" }, value = { 110 FORMAT_APK, 111 FORMAT_IDMAP, 112 FORMAT_ARSC, 113 FORMAT_DIR 114 }) 115 @Retention(RetentionPolicy.SOURCE) 116 public @interface FormatType {} 117 118 @GuardedBy("this") 119 private long mNativePtr; // final, except cleared in finalizer. 120 121 @Nullable 122 @GuardedBy("this") 123 private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. 124 125 @PropertyFlags 126 private final int mFlags; 127 128 @Nullable 129 private final AssetsProvider mAssets; 130 131 /** 132 * Creates a new ApkAssets instance from the given path on disk. 133 * 134 * @param path The path to an APK on disk. 135 * @return a new instance of ApkAssets. 136 * @throws IOException if a disk I/O error or parsing error occurred. 137 */ loadFromPath(@onNull String path)138 public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { 139 return loadFromPath(path, 0 /* flags */); 140 } 141 142 /** 143 * Creates a new ApkAssets instance from the given path on disk. 144 * 145 * @param path The path to an APK on disk. 146 * @param flags flags that change the behavior of loaded apk assets 147 * @return a new instance of ApkAssets. 148 * @throws IOException if a disk I/O error or parsing error occurred. 149 */ loadFromPath(@onNull String path, @PropertyFlags int flags)150 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags) 151 throws IOException { 152 return new ApkAssets(FORMAT_APK, path, flags, null /* assets */); 153 } 154 155 /** 156 * Creates a new ApkAssets instance from the given path on disk. 157 * 158 * @param path The path to an APK on disk. 159 * @param flags flags that change the behavior of loaded apk assets 160 * @param assets The assets provider that overrides the loading of file-based resources 161 * @return a new instance of ApkAssets. 162 * @throws IOException if a disk I/O error or parsing error occurred. 163 */ loadFromPath(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)164 public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags, 165 @Nullable AssetsProvider assets) throws IOException { 166 return new ApkAssets(FORMAT_APK, path, flags, assets); 167 } 168 169 /** 170 * Creates a new ApkAssets instance from the given file descriptor. 171 * 172 * Performs a dup of the underlying fd, so you must take care of still closing 173 * the FileDescriptor yourself (and can do that whenever you want). 174 * 175 * @param fd The FileDescriptor of an open, readable APK. 176 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 177 * @param flags flags that change the behavior of loaded apk assets 178 * @param assets The assets provider that overrides the loading of file-based resources 179 * @return a new instance of ApkAssets. 180 * @throws IOException if a disk I/O error or parsing error occurred. 181 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)182 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 183 @NonNull String friendlyName, @PropertyFlags int flags, 184 @Nullable AssetsProvider assets) throws IOException { 185 return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets); 186 } 187 188 /** 189 * Creates a new ApkAssets instance from the given file descriptor. 190 * 191 * Performs a dup of the underlying fd, so you must take care of still closing 192 * the FileDescriptor yourself (and can do that whenever you want). 193 * 194 * @param fd The FileDescriptor of an open, readable APK. 195 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 196 * @param offset The location within the file that the apk starts. This must be 0 if length is 197 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 198 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 199 * if it extends to the end of the file. 200 * @param flags flags that change the behavior of loaded apk assets 201 * @param assets The assets provider that overrides the loading of file-based resources 202 * @return a new instance of ApkAssets. 203 * @throws IOException if a disk I/O error or parsing error occurred. 204 */ loadFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)205 public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, 206 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 207 @Nullable AssetsProvider assets) 208 throws IOException { 209 return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); 210 } 211 212 /** 213 * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path 214 * is encoded within the IDMAP. 215 * 216 * @param idmapPath Path to the IDMAP of an overlay APK. 217 * @param flags flags that change the behavior of loaded apk assets 218 * @return a new instance of ApkAssets. 219 * @throws IOException if a disk I/O error or parsing error occurred. 220 */ loadOverlayFromPath(@onNull String idmapPath, @PropertyFlags int flags)221 public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, 222 @PropertyFlags int flags) throws IOException { 223 return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */); 224 } 225 226 /** 227 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 228 * for use with a {@link ResourcesProvider}. 229 * 230 * Performs a dup of the underlying fd, so you must take care of still closing 231 * the FileDescriptor yourself (and can do that whenever you want). 232 * 233 * @param fd The FileDescriptor of an open, readable resources.arsc. 234 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 235 * @param flags flags that change the behavior of loaded apk assets 236 * @param assets The assets provider that overrides the loading of file-based resources 237 * @return a new instance of ApkAssets. 238 * @throws IOException if a disk I/O error or parsing error occurred. 239 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)240 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 241 @NonNull String friendlyName, @PropertyFlags int flags, 242 @Nullable AssetsProvider assets) throws IOException { 243 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets); 244 } 245 246 /** 247 * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc 248 * for use with a {@link ResourcesProvider}. 249 * 250 * Performs a dup of the underlying fd, so you must take care of still closing 251 * the FileDescriptor yourself (and can do that whenever you want). 252 * 253 * @param fd The FileDescriptor of an open, readable resources.arsc. 254 * @param friendlyName The friendly name used to identify this ApkAssets when logging. 255 * @param offset The location within the file that the table starts. This must be 0 if length is 256 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 257 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 258 * if it extends to the end of the file. 259 * @param flags flags that change the behavior of loaded apk assets 260 * @param assets The assets provider that overrides the loading of file-based resources 261 * @return a new instance of ApkAssets. 262 * @throws IOException if a disk I/O error or parsing error occurred. 263 */ loadTableFromFd(@onNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)264 public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, 265 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 266 @Nullable AssetsProvider assets) throws IOException { 267 return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets); 268 } 269 270 /** 271 * Creates a new ApkAssets instance from the given directory path. The directory should have the 272 * file structure of an APK. 273 * 274 * @param path The path to a directory on disk. 275 * @param flags flags that change the behavior of loaded apk assets 276 * @param assets The assets provider that overrides the loading of file-based resources 277 * @return a new instance of ApkAssets. 278 * @throws IOException if a disk I/O error or parsing error occurred. 279 */ loadFromDir(@onNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)280 public static @NonNull ApkAssets loadFromDir(@NonNull String path, 281 @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { 282 return new ApkAssets(FORMAT_DIR, path, flags, assets); 283 } 284 285 /** 286 * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence 287 * is required for a lot of APIs, and it's easier to have a non-null reference rather than 288 * tracking a separate identifier. 289 * 290 * @param flags flags that change the behavior of loaded apk assets 291 * @param assets The assets provider that overrides the loading of file-based resources 292 */ 293 @NonNull loadEmptyForLoader(@ropertyFlags int flags, @Nullable AssetsProvider assets)294 public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, 295 @Nullable AssetsProvider assets) { 296 return new ApkAssets(flags, assets); 297 } 298 ApkAssets(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets)299 private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, 300 @Nullable AssetsProvider assets) throws IOException { 301 Objects.requireNonNull(path, "path"); 302 mFlags = flags; 303 mNativePtr = nativeLoad(format, path, flags, assets); 304 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 305 mAssets = assets; 306 } 307 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)308 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 309 @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) 310 throws IOException { 311 Objects.requireNonNull(fd, "fd"); 312 Objects.requireNonNull(friendlyName, "friendlyName"); 313 mFlags = flags; 314 mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); 315 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 316 mAssets = assets; 317 } 318 ApkAssets(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets)319 private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, 320 @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, 321 @Nullable AssetsProvider assets) throws IOException { 322 Objects.requireNonNull(fd, "fd"); 323 Objects.requireNonNull(friendlyName, "friendlyName"); 324 mFlags = flags; 325 mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); 326 mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); 327 mAssets = assets; 328 } 329 ApkAssets(@ropertyFlags int flags, @Nullable AssetsProvider assets)330 private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { 331 mFlags = flags; 332 mNativePtr = nativeLoadEmpty(flags, assets); 333 mStringBlock = null; 334 mAssets = assets; 335 } 336 337 @UnsupportedAppUsage getAssetPath()338 public @NonNull String getAssetPath() { 339 synchronized (this) { 340 return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr)); 341 } 342 } 343 344 /** @hide */ getDebugName()345 public @NonNull String getDebugName() { 346 synchronized (this) { 347 return nativeGetDebugName(mNativePtr); 348 } 349 } 350 351 @Nullable getStringFromPool(int idx)352 CharSequence getStringFromPool(int idx) { 353 if (mStringBlock == null) { 354 return null; 355 } 356 357 synchronized (this) { 358 return mStringBlock.getSequence(idx); 359 } 360 } 361 362 /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */ isForLoader()363 public boolean isForLoader() { 364 return (mFlags & PROPERTY_LOADER) != 0; 365 } 366 367 /** 368 * Returns the assets provider that overrides the loading of assets present in this apk assets. 369 */ 370 @Nullable getAssetsProvider()371 public AssetsProvider getAssetsProvider() { 372 return mAssets; 373 } 374 375 /** 376 * Retrieve a parser for a compiled XML file. This is associated with a single APK and 377 * <em>NOT</em> a full AssetManager. This means that shared-library references will not be 378 * dynamically assigned runtime package IDs. 379 * 380 * @param fileName The path to the file within the APK. 381 * @return An XmlResourceParser. 382 * @throws IOException if the file was not found or an error occurred retrieving it. 383 */ openXml(@onNull String fileName)384 public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException { 385 Objects.requireNonNull(fileName, "fileName"); 386 synchronized (this) { 387 long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); 388 try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { 389 XmlResourceParser parser = block.newParser(); 390 // If nativeOpenXml doesn't throw, it will always return a valid native pointer, 391 // which makes newParser always return non-null. But let's be careful. 392 if (parser == null) { 393 throw new AssertionError("block.newParser() returned a null parser"); 394 } 395 return parser; 396 } 397 } 398 } 399 400 /** @hide */ 401 @Nullable getOverlayableInfo(String overlayableName)402 public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException { 403 synchronized (this) { 404 return nativeGetOverlayableInfo(mNativePtr, overlayableName); 405 } 406 } 407 408 /** @hide */ definesOverlayable()409 public boolean definesOverlayable() throws IOException { 410 synchronized (this) { 411 return nativeDefinesOverlayable(mNativePtr); 412 } 413 } 414 415 /** 416 * Returns false if the underlying APK was changed since this ApkAssets was loaded. 417 */ isUpToDate()418 public boolean isUpToDate() { 419 synchronized (this) { 420 return nativeIsUpToDate(mNativePtr); 421 } 422 } 423 424 @Override toString()425 public String toString() { 426 return "ApkAssets{path=" + getDebugName() + "}"; 427 } 428 429 @Override finalize()430 protected void finalize() throws Throwable { 431 close(); 432 } 433 434 /** 435 * Closes this class and the contained {@link #mStringBlock}. 436 */ close()437 public void close() { 438 synchronized (this) { 439 if (mNativePtr != 0) { 440 if (mStringBlock != null) { 441 mStringBlock.close(); 442 } 443 nativeDestroy(mNativePtr); 444 mNativePtr = 0; 445 } 446 } 447 } 448 dump(PrintWriter pw, String prefix)449 void dump(PrintWriter pw, String prefix) { 450 pw.println(prefix + "class=" + getClass()); 451 pw.println(prefix + "debugName=" + getDebugName()); 452 pw.println(prefix + "assetPath=" + getAssetPath()); 453 } 454 nativeLoad(@ormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset)455 private static native long nativeLoad(@FormatType int format, @NonNull String path, 456 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeLoadEmpty(@ropertyFlags int flags, @Nullable AssetsProvider asset)457 private static native long nativeLoadEmpty(@PropertyFlags int flags, 458 @Nullable AssetsProvider asset); nativeLoadFd(@ormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider asset)459 private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, 460 @NonNull String friendlyName, @PropertyFlags int flags, 461 @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)462 private static native long nativeLoadFdOffsets(@FormatType int format, 463 @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, 464 @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; nativeDestroy(long ptr)465 private static native void nativeDestroy(long ptr); nativeGetAssetPath(long ptr)466 private static native @NonNull String nativeGetAssetPath(long ptr); nativeGetDebugName(long ptr)467 private static native @NonNull String nativeGetDebugName(long ptr); nativeGetStringBlock(long ptr)468 private static native long nativeGetStringBlock(long ptr); nativeIsUpToDate(long ptr)469 @CriticalNative private static native boolean nativeIsUpToDate(long ptr); nativeOpenXml(long ptr, @NonNull String fileName)470 private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; nativeGetOverlayableInfo(long ptr, String overlayableName)471 private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, 472 String overlayableName) throws IOException; nativeDefinesOverlayable(long ptr)473 private static native boolean nativeDefinesOverlayable(long ptr) throws IOException; 474 } 475