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