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