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