475 lines
19 KiB
Java
475 lines
19 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2017 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
package android.content.res;
|
||
|
|
||
|
import android.annotation.IntDef;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.om.OverlayableInfo;
|
||
|
import android.content.res.loader.AssetsProvider;
|
||
|
import android.content.res.loader.ResourcesProvider;
|
||
|
import android.text.TextUtils;
|
||
|
|
||
|
import com.android.internal.annotations.GuardedBy;
|
||
|
|
||
|
import dalvik.annotation.optimization.CriticalNative;
|
||
|
|
||
|
import java.io.FileDescriptor;
|
||
|
import java.io.IOException;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.lang.annotation.RetentionPolicy;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
/**
|
||
|
* The loaded, immutable, in-memory representation of an APK.
|
||
|
*
|
||
|
* The main implementation is native C++ and there is very little API surface exposed here. The APK
|
||
|
* is mainly accessed via {@link AssetManager}.
|
||
|
*
|
||
|
* Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
|
||
|
* making the creation of AssetManagers very cheap.
|
||
|
* @hide
|
||
|
*/
|
||
|
public final class ApkAssets {
|
||
|
|
||
|
/**
|
||
|
* The apk assets contains framework resource values specified by the system.
|
||
|
* This allows some functions to filter out this package when computing what
|
||
|
* configurations/resources are available.
|
||
|
*/
|
||
|
public static final int PROPERTY_SYSTEM = 1 << 0;
|
||
|
|
||
|
/**
|
||
|
* The apk assets is a shared library or was loaded as a shared library by force.
|
||
|
* The package ids of dynamic apk assets are assigned at runtime instead of compile time.
|
||
|
*/
|
||
|
public static final int PROPERTY_DYNAMIC = 1 << 1;
|
||
|
|
||
|
/**
|
||
|
* The apk assets has been loaded dynamically using a {@link ResourcesProvider}.
|
||
|
* Loader apk assets overlay resources like RROs except they are not backed by an idmap.
|
||
|
*/
|
||
|
public static final int PROPERTY_LOADER = 1 << 2;
|
||
|
|
||
|
/**
|
||
|
* The apk assets is a RRO.
|
||
|
* An RRO overlays resource values of its target package.
|
||
|
*/
|
||
|
private static final int PROPERTY_OVERLAY = 1 << 3;
|
||
|
|
||
|
/**
|
||
|
* The apk assets is owned by the application running in this process and incremental crash
|
||
|
* protections for this APK must be disabled.
|
||
|
*/
|
||
|
public static final int PROPERTY_DISABLE_INCREMENTAL_HARDENING = 1 << 4;
|
||
|
|
||
|
/**
|
||
|
* The apk assets only contain the overlayable declarations information.
|
||
|
*/
|
||
|
public static final int PROPERTY_ONLY_OVERLAYABLES = 1 << 5;
|
||
|
|
||
|
/** Flags that change the behavior of loaded apk assets. */
|
||
|
@IntDef(prefix = { "PROPERTY_" }, value = {
|
||
|
PROPERTY_SYSTEM,
|
||
|
PROPERTY_DYNAMIC,
|
||
|
PROPERTY_LOADER,
|
||
|
PROPERTY_OVERLAY,
|
||
|
})
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
public @interface PropertyFlags {}
|
||
|
|
||
|
/** The path used to load the apk assets represents an APK file. */
|
||
|
private static final int FORMAT_APK = 0;
|
||
|
|
||
|
/** The path used to load the apk assets represents an idmap file. */
|
||
|
private static final int FORMAT_IDMAP = 1;
|
||
|
|
||
|
/** The path used to load the apk assets represents an resources.arsc file. */
|
||
|
private static final int FORMAT_ARSC = 2;
|
||
|
|
||
|
/** the path used to load the apk assets represents a directory. */
|
||
|
private static final int FORMAT_DIR = 3;
|
||
|
|
||
|
// Format types that change how the apk assets are loaded.
|
||
|
@IntDef(prefix = { "FORMAT_" }, value = {
|
||
|
FORMAT_APK,
|
||
|
FORMAT_IDMAP,
|
||
|
FORMAT_ARSC,
|
||
|
FORMAT_DIR
|
||
|
})
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
public @interface FormatType {}
|
||
|
|
||
|
@GuardedBy("this")
|
||
|
private long mNativePtr; // final, except cleared in finalizer.
|
||
|
|
||
|
@Nullable
|
||
|
@GuardedBy("this")
|
||
|
private final StringBlock mStringBlock; // null or closed if mNativePtr = 0.
|
||
|
|
||
|
@PropertyFlags
|
||
|
private final int mFlags;
|
||
|
|
||
|
@Nullable
|
||
|
private final AssetsProvider mAssets;
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given path on disk.
|
||
|
*
|
||
|
* @param path The path to an APK on disk.
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
|
||
|
return loadFromPath(path, 0 /* flags */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given path on disk.
|
||
|
*
|
||
|
* @param path The path to an APK on disk.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
|
||
|
throws IOException {
|
||
|
return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given path on disk.
|
||
|
*
|
||
|
* @param path The path to an APK on disk.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
return new ApkAssets(FORMAT_APK, path, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given file descriptor.
|
||
|
*
|
||
|
* Performs a dup of the underlying fd, so you must take care of still closing
|
||
|
* the FileDescriptor yourself (and can do that whenever you want).
|
||
|
*
|
||
|
* @param fd The FileDescriptor of an open, readable APK.
|
||
|
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given file descriptor.
|
||
|
*
|
||
|
* Performs a dup of the underlying fd, so you must take care of still closing
|
||
|
* the FileDescriptor yourself (and can do that whenever you want).
|
||
|
*
|
||
|
* @param fd The FileDescriptor of an open, readable APK.
|
||
|
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
|
||
|
* @param offset The location within the file that the apk starts. This must be 0 if length is
|
||
|
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
|
||
|
* @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
|
||
|
* if it extends to the end of the file.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets)
|
||
|
throws IOException {
|
||
|
return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
|
||
|
* is encoded within the IDMAP.
|
||
|
*
|
||
|
* @param idmapPath Path to the IDMAP of an overlay APK.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
|
||
|
@PropertyFlags int flags) throws IOException {
|
||
|
return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
|
||
|
* for use with a {@link ResourcesProvider}.
|
||
|
*
|
||
|
* Performs a dup of the underlying fd, so you must take care of still closing
|
||
|
* the FileDescriptor yourself (and can do that whenever you want).
|
||
|
*
|
||
|
* @param fd The FileDescriptor of an open, readable resources.arsc.
|
||
|
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
|
||
|
* for use with a {@link ResourcesProvider}.
|
||
|
*
|
||
|
* Performs a dup of the underlying fd, so you must take care of still closing
|
||
|
* the FileDescriptor yourself (and can do that whenever you want).
|
||
|
*
|
||
|
* @param fd The FileDescriptor of an open, readable resources.arsc.
|
||
|
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
|
||
|
* @param offset The location within the file that the table starts. This must be 0 if length is
|
||
|
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
|
||
|
* @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
|
||
|
* if it extends to the end of the file.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a new ApkAssets instance from the given directory path. The directory should have the
|
||
|
* file structure of an APK.
|
||
|
*
|
||
|
* @param path The path to a directory on disk.
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
* @return a new instance of ApkAssets.
|
||
|
* @throws IOException if a disk I/O error or parsing error occurred.
|
||
|
*/
|
||
|
public static @NonNull ApkAssets loadFromDir(@NonNull String path,
|
||
|
@PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
|
||
|
return new ApkAssets(FORMAT_DIR, path, flags, assets);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
|
||
|
* is required for a lot of APIs, and it's easier to have a non-null reference rather than
|
||
|
* tracking a separate identifier.
|
||
|
*
|
||
|
* @param flags flags that change the behavior of loaded apk assets
|
||
|
* @param assets The assets provider that overrides the loading of file-based resources
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) {
|
||
|
return new ApkAssets(flags, assets);
|
||
|
}
|
||
|
|
||
|
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
Objects.requireNonNull(path, "path");
|
||
|
mFlags = flags;
|
||
|
mNativePtr = nativeLoad(format, path, flags, assets);
|
||
|
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
|
||
|
mAssets = assets;
|
||
|
}
|
||
|
|
||
|
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
|
||
|
throws IOException {
|
||
|
Objects.requireNonNull(fd, "fd");
|
||
|
Objects.requireNonNull(friendlyName, "friendlyName");
|
||
|
mFlags = flags;
|
||
|
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
|
||
|
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
|
||
|
mAssets = assets;
|
||
|
}
|
||
|
|
||
|
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider assets) throws IOException {
|
||
|
Objects.requireNonNull(fd, "fd");
|
||
|
Objects.requireNonNull(friendlyName, "friendlyName");
|
||
|
mFlags = flags;
|
||
|
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
|
||
|
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
|
||
|
mAssets = assets;
|
||
|
}
|
||
|
|
||
|
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
|
||
|
mFlags = flags;
|
||
|
mNativePtr = nativeLoadEmpty(flags, assets);
|
||
|
mStringBlock = null;
|
||
|
mAssets = assets;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
public @NonNull String getAssetPath() {
|
||
|
synchronized (this) {
|
||
|
return TextUtils.emptyIfNull(nativeGetAssetPath(mNativePtr));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public @NonNull String getDebugName() {
|
||
|
synchronized (this) {
|
||
|
return nativeGetDebugName(mNativePtr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
CharSequence getStringFromPool(int idx) {
|
||
|
if (mStringBlock == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
synchronized (this) {
|
||
|
return mStringBlock.getSequence(idx);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */
|
||
|
public boolean isForLoader() {
|
||
|
return (mFlags & PROPERTY_LOADER) != 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the assets provider that overrides the loading of assets present in this apk assets.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public AssetsProvider getAssetsProvider() {
|
||
|
return mAssets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieve a parser for a compiled XML file. This is associated with a single APK and
|
||
|
* <em>NOT</em> a full AssetManager. This means that shared-library references will not be
|
||
|
* dynamically assigned runtime package IDs.
|
||
|
*
|
||
|
* @param fileName The path to the file within the APK.
|
||
|
* @return An XmlResourceParser.
|
||
|
* @throws IOException if the file was not found or an error occurred retrieving it.
|
||
|
*/
|
||
|
public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
|
||
|
Objects.requireNonNull(fileName, "fileName");
|
||
|
synchronized (this) {
|
||
|
long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
|
||
|
try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
|
||
|
XmlResourceParser parser = block.newParser();
|
||
|
// If nativeOpenXml doesn't throw, it will always return a valid native pointer,
|
||
|
// which makes newParser always return non-null. But let's be careful.
|
||
|
if (parser == null) {
|
||
|
throw new AssertionError("block.newParser() returned a null parser");
|
||
|
}
|
||
|
return parser;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@Nullable
|
||
|
public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
|
||
|
synchronized (this) {
|
||
|
return nativeGetOverlayableInfo(mNativePtr, overlayableName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public boolean definesOverlayable() throws IOException {
|
||
|
synchronized (this) {
|
||
|
return nativeDefinesOverlayable(mNativePtr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
|
||
|
*/
|
||
|
public boolean isUpToDate() {
|
||
|
synchronized (this) {
|
||
|
return nativeIsUpToDate(mNativePtr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return "ApkAssets{path=" + getDebugName() + "}";
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() throws Throwable {
|
||
|
close();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closes this class and the contained {@link #mStringBlock}.
|
||
|
*/
|
||
|
public void close() {
|
||
|
synchronized (this) {
|
||
|
if (mNativePtr != 0) {
|
||
|
if (mStringBlock != null) {
|
||
|
mStringBlock.close();
|
||
|
}
|
||
|
nativeDestroy(mNativePtr);
|
||
|
mNativePtr = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void dump(PrintWriter pw, String prefix) {
|
||
|
pw.println(prefix + "class=" + getClass());
|
||
|
pw.println(prefix + "debugName=" + getDebugName());
|
||
|
pw.println(prefix + "assetPath=" + getAssetPath());
|
||
|
}
|
||
|
|
||
|
private static native long nativeLoad(@FormatType int format, @NonNull String path,
|
||
|
@PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
|
||
|
private static native long nativeLoadEmpty(@PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider asset);
|
||
|
private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd,
|
||
|
@NonNull String friendlyName, @PropertyFlags int flags,
|
||
|
@Nullable AssetsProvider asset) throws IOException;
|
||
|
private static native long nativeLoadFdOffsets(@FormatType int format,
|
||
|
@NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length,
|
||
|
@PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
|
||
|
private static native void nativeDestroy(long ptr);
|
||
|
private static native @NonNull String nativeGetAssetPath(long ptr);
|
||
|
private static native @NonNull String nativeGetDebugName(long ptr);
|
||
|
private static native long nativeGetStringBlock(long ptr);
|
||
|
@CriticalNative private static native boolean nativeIsUpToDate(long ptr);
|
||
|
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
|
||
|
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
|
||
|
String overlayableName) throws IOException;
|
||
|
private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
|
||
|
}
|