1746 lines
64 KiB
Java
1746 lines
64 KiB
Java
/*
|
|
* Copyright (C) 2006 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 static android.content.res.Resources.ID_NULL;
|
|
|
|
import android.annotation.AnyRes;
|
|
import android.annotation.ArrayRes;
|
|
import android.annotation.AttrRes;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.StringRes;
|
|
import android.annotation.StyleRes;
|
|
import android.annotation.TestApi;
|
|
import android.compat.annotation.UnsupportedAppUsage;
|
|
import android.content.pm.ActivityInfo;
|
|
import android.content.res.Configuration.NativeConfig;
|
|
import android.content.res.loader.ResourcesLoader;
|
|
import android.os.Build;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.util.ArraySet;
|
|
import android.util.Log;
|
|
import android.util.SparseArray;
|
|
import android.util.TypedValue;
|
|
|
|
import com.android.internal.annotations.GuardedBy;
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.internal.content.om.OverlayConfig;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.PrintWriter;
|
|
import java.lang.ref.Reference;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* Provides access to an application's raw asset files; see {@link Resources}
|
|
* for the way most applications will want to retrieve their resource data.
|
|
* This class presents a lower-level API that allows you to open and read raw
|
|
* files that have been bundled with the application as a simple stream of
|
|
* bytes.
|
|
*/
|
|
public final class AssetManager implements AutoCloseable {
|
|
private static final String TAG = "AssetManager";
|
|
private static final boolean DEBUG_REFS = false;
|
|
|
|
private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
|
|
|
|
private static final Object sSync = new Object();
|
|
|
|
private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0];
|
|
|
|
// Not private for LayoutLib's BridgeAssetManager.
|
|
@UnsupportedAppUsage
|
|
@GuardedBy("sSync") static AssetManager sSystem = null;
|
|
|
|
@GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
|
|
@GuardedBy("sSync") private static ArraySet<ApkAssets> sSystemApkAssetsSet;
|
|
|
|
/**
|
|
* Cookie value to use when the actual cookie is unknown. This value tells the system to search
|
|
* all the ApkAssets for the asset.
|
|
* @hide
|
|
*/
|
|
public static final int COOKIE_UNKNOWN = -1;
|
|
|
|
/**
|
|
* Mode for {@link #open(String, int)}: no specific information about how
|
|
* data will be accessed.
|
|
*/
|
|
public static final int ACCESS_UNKNOWN = 0;
|
|
/**
|
|
* Mode for {@link #open(String, int)}: Read chunks, and seek forward and
|
|
* backward.
|
|
*/
|
|
public static final int ACCESS_RANDOM = 1;
|
|
/**
|
|
* Mode for {@link #open(String, int)}: Read sequentially, with an
|
|
* occasional forward seek.
|
|
*/
|
|
public static final int ACCESS_STREAMING = 2;
|
|
/**
|
|
* Mode for {@link #open(String, int)}: Attempt to load contents into
|
|
* memory, for fast small reads.
|
|
*/
|
|
public static final int ACCESS_BUFFER = 3;
|
|
|
|
@GuardedBy("this") private final TypedValue mValue = new TypedValue();
|
|
@GuardedBy("this") private final long[] mOffsets = new long[2];
|
|
|
|
// Pointer to native implementation, stuffed inside a long.
|
|
@UnsupportedAppUsage
|
|
@GuardedBy("this") private long mObject;
|
|
|
|
// The loaded asset paths.
|
|
@GuardedBy("this") private ApkAssets[] mApkAssets;
|
|
|
|
// Debug/reference counting implementation.
|
|
@GuardedBy("this") private boolean mOpen = true;
|
|
@GuardedBy("this") private int mNumRefs = 1;
|
|
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
|
|
|
|
private ResourcesLoader[] mLoaders;
|
|
|
|
/**
|
|
* A Builder class that helps create an AssetManager with only a single invocation of
|
|
* {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
|
|
* AssetManager must ensure there are system ApkAssets loaded at all times, which when combined
|
|
* with the user's call to add additional ApkAssets, results in multiple calls to
|
|
* {@link AssetManager#setApkAssets(ApkAssets[], boolean)}.
|
|
* @hide
|
|
*/
|
|
public static class Builder {
|
|
private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
|
|
private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
|
|
|
|
private boolean mNoInit = false;
|
|
|
|
public Builder addApkAssets(ApkAssets apkAssets) {
|
|
mUserApkAssets.add(apkAssets);
|
|
return this;
|
|
}
|
|
|
|
public Builder addLoader(ResourcesLoader loader) {
|
|
mLoaders.add(loader);
|
|
return this;
|
|
}
|
|
|
|
public Builder setNoInit() {
|
|
mNoInit = true;
|
|
return this;
|
|
}
|
|
|
|
public AssetManager build() {
|
|
// Retrieving the system ApkAssets forces their creation as well.
|
|
final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
|
|
|
|
// Filter ApkAssets so that assets provided by multiple loaders are only included once
|
|
// in the AssetManager assets. The last appearance of the ApkAssets dictates its load
|
|
// order.
|
|
final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
|
|
final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
|
|
for (int i = mLoaders.size() - 1; i >= 0; i--) {
|
|
final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
|
|
for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
|
|
final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
|
|
if (uniqueLoaderApkAssets.add(apkAssets)) {
|
|
loaderApkAssets.add(0, apkAssets);
|
|
}
|
|
}
|
|
}
|
|
|
|
final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
|
|
+ loaderApkAssets.size();
|
|
final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
|
|
|
|
System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
|
|
|
|
// Append user ApkAssets after system ApkAssets.
|
|
for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
|
|
apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
|
|
}
|
|
|
|
// Append ApkAssets provided by loaders to the end.
|
|
for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
|
|
apkAssets[i + systemApkAssets.length + mUserApkAssets.size()] =
|
|
loaderApkAssets.get(i);
|
|
}
|
|
|
|
// Calling this constructor prevents creation of system ApkAssets, which we took care
|
|
// of in this Builder.
|
|
final AssetManager assetManager = new AssetManager(false /*sentinel*/);
|
|
assetManager.mApkAssets = apkAssets;
|
|
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
|
|
false /*invalidateCaches*/, mNoInit /*preset*/);
|
|
assetManager.mLoaders = mLoaders.isEmpty() ? null
|
|
: mLoaders.toArray(new ResourcesLoader[0]);
|
|
|
|
return assetManager;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new AssetManager containing only the basic system assets.
|
|
* Applications will not generally use this method, instead retrieving the
|
|
* appropriate asset manager with {@link Resources#getAssets}. Not for
|
|
* use by applications.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public AssetManager() {
|
|
final ApkAssets[] assets;
|
|
synchronized (sSync) {
|
|
createSystemAssetsInZygoteLocked(false, FRAMEWORK_APK_PATH);
|
|
assets = sSystemApkAssets;
|
|
}
|
|
|
|
mObject = nativeCreate();
|
|
if (DEBUG_REFS) {
|
|
mNumRefs = 0;
|
|
incRefsLocked(hashCode());
|
|
}
|
|
|
|
// Always set the framework resources.
|
|
setApkAssets(assets, false /*invalidateCaches*/);
|
|
}
|
|
|
|
/**
|
|
* Private constructor that doesn't call ensureSystemAssets.
|
|
* Used for the creation of system assets.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private AssetManager(boolean sentinel) {
|
|
mObject = nativeCreate();
|
|
if (DEBUG_REFS) {
|
|
mNumRefs = 0;
|
|
incRefsLocked(hashCode());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This must be called from Zygote so that system assets are shared by all applications.
|
|
* @hide
|
|
*/
|
|
@GuardedBy("sSync")
|
|
@VisibleForTesting
|
|
public static void createSystemAssetsInZygoteLocked(boolean reinitialize,
|
|
String frameworkPath) {
|
|
if (sSystem != null && !reinitialize) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
|
|
apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM));
|
|
|
|
final String[] systemIdmapPaths =
|
|
OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
|
|
for (String idmapPath : systemIdmapPaths) {
|
|
apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM));
|
|
}
|
|
|
|
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
|
|
sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
|
|
if (sSystem == null) {
|
|
sSystem = new AssetManager(true /*sentinel*/);
|
|
}
|
|
sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
|
|
} catch (IOException e) {
|
|
throw new IllegalStateException("Failed to create system AssetManager", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a global shared asset manager that provides access to only
|
|
* system assets (no application assets).
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static AssetManager getSystem() {
|
|
synchronized (sSync) {
|
|
createSystemAssetsInZygoteLocked(false, FRAMEWORK_APK_PATH);
|
|
return sSystem;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close this asset manager.
|
|
*/
|
|
@Override
|
|
public void close() {
|
|
synchronized (this) {
|
|
if (!mOpen) {
|
|
return;
|
|
}
|
|
|
|
mOpen = false;
|
|
decRefsLocked(hashCode());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)}
|
|
* family of methods.
|
|
*
|
|
* @param apkAssets The new set of paths.
|
|
* @param invalidateCaches Whether to invalidate any caches. This should almost always be true.
|
|
* Set this to false if you are appending new resources
|
|
* (not new configurations).
|
|
* @hide
|
|
*/
|
|
public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
|
|
Objects.requireNonNull(apkAssets, "apkAssets");
|
|
|
|
ApkAssets[] newApkAssets = new ApkAssets[sSystemApkAssets.length + apkAssets.length];
|
|
|
|
// Copy the system assets first.
|
|
System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);
|
|
|
|
// Copy the given ApkAssets if they are not already in the system list.
|
|
int newLength = sSystemApkAssets.length;
|
|
for (ApkAssets apkAsset : apkAssets) {
|
|
if (!sSystemApkAssetsSet.contains(apkAsset)) {
|
|
newApkAssets[newLength++] = apkAsset;
|
|
}
|
|
}
|
|
|
|
// Truncate if necessary.
|
|
if (newLength != newApkAssets.length) {
|
|
newApkAssets = Arrays.copyOf(newApkAssets, newLength);
|
|
}
|
|
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
mApkAssets = newApkAssets;
|
|
nativeSetApkAssets(mObject, mApkAssets, invalidateCaches, false);
|
|
if (invalidateCaches) {
|
|
// Invalidate all caches.
|
|
invalidateCachesLocked(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Changes the {@link ResourcesLoader ResourcesLoaders} used in this AssetManager.
|
|
* @hide
|
|
*/
|
|
void setLoaders(@NonNull List<ResourcesLoader> newLoaders) {
|
|
Objects.requireNonNull(newLoaders, "newLoaders");
|
|
|
|
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
|
|
for (int i = 0; i < mApkAssets.length; i++) {
|
|
// Filter out the previous loader apk assets.
|
|
if (!mApkAssets[i].isForLoader()) {
|
|
apkAssets.add(mApkAssets[i]);
|
|
}
|
|
}
|
|
|
|
if (!newLoaders.isEmpty()) {
|
|
// Filter so that assets provided by multiple loaders are only included once
|
|
// in the final assets list. The last appearance of the ApkAssets dictates its load
|
|
// order.
|
|
final int loaderStartIndex = apkAssets.size();
|
|
final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
|
|
for (int i = newLoaders.size() - 1; i >= 0; i--) {
|
|
final List<ApkAssets> currentLoaderApkAssets = newLoaders.get(i).getApkAssets();
|
|
for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
|
|
final ApkAssets loaderApkAssets = currentLoaderApkAssets.get(j);
|
|
if (uniqueLoaderApkAssets.add(loaderApkAssets)) {
|
|
apkAssets.add(loaderStartIndex, loaderApkAssets);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mLoaders = newLoaders.toArray(new ResourcesLoader[0]);
|
|
setApkAssets(apkAssets.toArray(new ApkAssets[0]), true /* invalidate_caches */);
|
|
}
|
|
|
|
/**
|
|
* Invalidates the caches in this AssetManager according to the bitmask `diff`.
|
|
*
|
|
* @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}.
|
|
* @see ActivityInfo.Config
|
|
*/
|
|
private void invalidateCachesLocked(int diff) {
|
|
// TODO(adamlesinski): Currently there are no caches to invalidate in Java code.
|
|
}
|
|
|
|
/**
|
|
* Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this
|
|
* returns a 0-length array.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public @NonNull ApkAssets[] getApkAssets() {
|
|
synchronized (this) {
|
|
if (mOpen) {
|
|
return mApkAssets;
|
|
}
|
|
}
|
|
return sEmptyApkAssets;
|
|
}
|
|
|
|
/** @hide */
|
|
@TestApi
|
|
public @NonNull String[] getApkPaths() {
|
|
synchronized (this) {
|
|
if (mOpen) {
|
|
String[] paths = new String[mApkAssets.length];
|
|
final int count = mApkAssets.length;
|
|
for (int i = 0; i < count; i++) {
|
|
paths[i] = mApkAssets[i].getAssetPath();
|
|
}
|
|
return paths;
|
|
}
|
|
}
|
|
return new String[0];
|
|
}
|
|
|
|
/**
|
|
* Returns a cookie for use with the other APIs of AssetManager.
|
|
* @return 0 if the path was not found, otherwise a positive integer cookie representing
|
|
* this path in the AssetManager.
|
|
* @hide
|
|
*/
|
|
public int findCookieForPath(@NonNull String path) {
|
|
Objects.requireNonNull(path, "path");
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
final int count = mApkAssets.length;
|
|
for (int i = 0; i < count; i++) {
|
|
if (path.equals(mApkAssets[i].getAssetPath())) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
|
|
* @hide
|
|
*/
|
|
@Deprecated
|
|
@UnsupportedAppUsage
|
|
public int addAssetPath(String path) {
|
|
return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
|
|
* @hide
|
|
*/
|
|
@Deprecated
|
|
@UnsupportedAppUsage
|
|
public int addAssetPathAsSharedLibrary(String path) {
|
|
return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
|
|
* @hide
|
|
*/
|
|
@Deprecated
|
|
@UnsupportedAppUsage
|
|
public int addOverlayPath(String path) {
|
|
return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public void addSharedLibraryPaths(@NonNull String[] paths) {
|
|
final int length = paths.length;
|
|
for (int i = 0; i < length; i++) {
|
|
addAssetPathInternal(paths[i], false, true);
|
|
}
|
|
}
|
|
|
|
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
|
|
Objects.requireNonNull(path, "path");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
final int count = mApkAssets.length;
|
|
|
|
// See if we already have it loaded.
|
|
for (int i = 0; i < count; i++) {
|
|
if (mApkAssets[i].getAssetPath().equals(path)) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
final ApkAssets assets;
|
|
try {
|
|
if (overlay) {
|
|
// TODO(b/70343104): This hardcoded path will be removed once
|
|
// addAssetPathInternal is deleted.
|
|
final String idmapPath = "/data/resource-cache/"
|
|
+ path.substring(1).replace('/', '@')
|
|
+ "@idmap";
|
|
assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */);
|
|
} else {
|
|
assets = ApkAssets.loadFromPath(path,
|
|
appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
|
|
}
|
|
} catch (IOException e) {
|
|
return 0;
|
|
}
|
|
|
|
mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
|
|
mApkAssets[count] = assets;
|
|
nativeSetApkAssets(mObject, mApkAssets, true, false);
|
|
invalidateCachesLocked(-1);
|
|
return count + 1;
|
|
}
|
|
}
|
|
|
|
/** @hide */
|
|
@NonNull
|
|
public List<ResourcesLoader> getLoaders() {
|
|
return mLoaders == null ? Collections.emptyList() : Arrays.asList(mLoaders);
|
|
}
|
|
|
|
/**
|
|
* Ensures that the native implementation has not been destroyed.
|
|
* The AssetManager may have been closed, but references to it still exist
|
|
* and therefore the native implementation is not destroyed.
|
|
*/
|
|
@GuardedBy("this")
|
|
private void ensureValidLocked() {
|
|
if (mObject == 0) {
|
|
throw new RuntimeException("AssetManager has been destroyed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that the AssetManager has not been explicitly closed. If this method passes,
|
|
* then this implies that ensureValidLocked() also passes.
|
|
*/
|
|
@GuardedBy("this")
|
|
private void ensureOpenLocked() {
|
|
// If mOpen is true, this implies that mObject != 0.
|
|
if (!mOpen) {
|
|
throw new RuntimeException("AssetManager has been closed");
|
|
}
|
|
// Let's still check if the native object exists, given all the memory corruptions.
|
|
if (mObject == 0) {
|
|
throw new RuntimeException("AssetManager is open but the native object is gone");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populates {@code outValue} with the data associated a particular
|
|
* resource identifier for the current configuration.
|
|
*
|
|
* @param resId the resource identifier to load
|
|
* @param densityDpi the density bucket for which to load the resource
|
|
* @param outValue the typed value in which to put the data
|
|
* @param resolveRefs {@code true} to resolve references, {@code false}
|
|
* to leave them unresolved
|
|
* @return {@code true} if the data was loaded into {@code outValue},
|
|
* {@code false} otherwise
|
|
*/
|
|
@UnsupportedAppUsage
|
|
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
|
|
boolean resolveRefs) {
|
|
Objects.requireNonNull(outValue, "outValue");
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
final int cookie = nativeGetResourceValue(
|
|
mObject, resId, (short) densityDpi, outValue, resolveRefs);
|
|
if (cookie <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Convert the changing configurations flags populated by native code.
|
|
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
|
|
outValue.changingConfigurations);
|
|
|
|
if (outValue.type == TypedValue.TYPE_STRING) {
|
|
if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the string value associated with a particular resource
|
|
* identifier for the current configuration.
|
|
*
|
|
* @param resId the resource identifier to load
|
|
* @return the string value, or {@code null}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
@Nullable CharSequence getResourceText(@StringRes int resId) {
|
|
synchronized (this) {
|
|
final TypedValue outValue = mValue;
|
|
if (getResourceValue(resId, 0, outValue, true)) {
|
|
return outValue.coerceToString();
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the string value associated with a particular resource
|
|
* identifier for the current configuration.
|
|
*
|
|
* @param resId the resource identifier to load
|
|
* @param bagEntryId the index into the bag to load
|
|
* @return the string value, or {@code null}
|
|
*/
|
|
@UnsupportedAppUsage
|
|
@Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
final TypedValue outValue = mValue;
|
|
final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue);
|
|
if (cookie <= 0) {
|
|
return null;
|
|
}
|
|
|
|
// Convert the changing configurations flags populated by native code.
|
|
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
|
|
outValue.changingConfigurations);
|
|
|
|
if (outValue.type == TypedValue.TYPE_STRING) {
|
|
return getPooledStringForCookie(cookie, outValue.data);
|
|
}
|
|
return outValue.coerceToString();
|
|
}
|
|
}
|
|
|
|
int getResourceArraySize(@ArrayRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceArraySize(mObject, resId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populates `outData` with array elements of `resId`. `outData` is normally
|
|
* used with
|
|
* {@link TypedArray}.
|
|
*
|
|
* Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES}
|
|
* long,
|
|
* with the indices of the data representing the type, value, asset cookie,
|
|
* resource ID,
|
|
* configuration change mask, and density of the element.
|
|
*
|
|
* @param resId The resource ID of an array resource.
|
|
* @param outData The array to populate with data.
|
|
* @return The length of the array.
|
|
*
|
|
* @see TypedArray#STYLE_TYPE
|
|
* @see TypedArray#STYLE_DATA
|
|
* @see TypedArray#STYLE_ASSET_COOKIE
|
|
* @see TypedArray#STYLE_RESOURCE_ID
|
|
* @see TypedArray#STYLE_CHANGING_CONFIGURATIONS
|
|
* @see TypedArray#STYLE_DENSITY
|
|
*/
|
|
int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) {
|
|
Objects.requireNonNull(outData, "outData");
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceArray(mObject, resId, outData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves the string array associated with a particular resource
|
|
* identifier for the current configuration.
|
|
*
|
|
* @param resId the resource identifier of the string array
|
|
* @return the string array, or {@code null}
|
|
*/
|
|
@Nullable String[] getResourceStringArray(@ArrayRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceStringArray(mObject, resId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the text array associated with a particular resource
|
|
* identifier.
|
|
*
|
|
* @param resId the resource id of the string array
|
|
*/
|
|
@Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId);
|
|
if (rawInfoArray == null) {
|
|
return null;
|
|
}
|
|
|
|
final int rawInfoArrayLen = rawInfoArray.length;
|
|
final int infoArrayLen = rawInfoArrayLen / 2;
|
|
final CharSequence[] retArray = new CharSequence[infoArrayLen];
|
|
for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
|
|
int cookie = rawInfoArray[i];
|
|
int index = rawInfoArray[i + 1];
|
|
retArray[j] = (index >= 0 && cookie > 0)
|
|
? getPooledStringForCookie(cookie, index) : null;
|
|
}
|
|
return retArray;
|
|
}
|
|
}
|
|
|
|
@Nullable int[] getResourceIntArray(@ArrayRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceIntArray(mObject, resId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the attributes for a style resource. These are the <item>
|
|
* elements in
|
|
* a <style> resource.
|
|
* @param resId The resource ID of the style
|
|
* @return An array of attribute IDs.
|
|
*/
|
|
@AttrRes int[] getStyleAttributes(@StyleRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetStyleAttributes(mObject, resId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populates {@code outValue} with the data associated with a particular
|
|
* resource identifier for the current configuration. Resolves theme
|
|
* attributes against the specified theme.
|
|
*
|
|
* @param theme the native pointer of the theme
|
|
* @param resId the resource identifier to load
|
|
* @param outValue the typed value in which to put the data
|
|
* @param resolveRefs {@code true} to resolve references, {@code false}
|
|
* to leave them unresolved
|
|
* @return {@code true} if the data was loaded into {@code outValue},
|
|
* {@code false} otherwise
|
|
*/
|
|
boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
|
|
boolean resolveRefs) {
|
|
Objects.requireNonNull(outValue, "outValue");
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
|
|
resolveRefs);
|
|
if (cookie <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Convert the changing configurations flags populated by native code.
|
|
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
|
|
outValue.changingConfigurations);
|
|
|
|
if (outValue.type == TypedValue.TYPE_STRING) {
|
|
if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void dumpTheme(long theme, int priority, String tag, String prefix) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
nativeThemeDump(mObject, theme, priority, tag, prefix);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
@Nullable String getResourceName(@AnyRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceName(mObject, resId);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
@Nullable String getResourcePackageName(@AnyRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourcePackageName(mObject, resId);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
@Nullable String getResourceTypeName(@AnyRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceTypeName(mObject, resId);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
@Nullable String getResourceEntryName(@AnyRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetResourceEntryName(mObject, resId);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
@AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType,
|
|
@Nullable String defPackage) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
// name is checked in JNI.
|
|
return nativeGetResourceIdentifier(mObject, name, defType, defPackage);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* To get the parent theme resource id according to the parameter theme resource id.
|
|
* @param resId theme resource id.
|
|
* @return the parent theme resource id.
|
|
* @hide
|
|
*/
|
|
@StyleRes
|
|
int getParentThemeIdentifier(@StyleRes int resId) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
// name is checked in JNI.
|
|
return nativeGetParentThemeIdentifier(mObject, resId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable resource resolution logging to track the steps taken to resolve the last resource
|
|
* entry retrieved. Stores the configuration and package names for each step.
|
|
*
|
|
* Default disabled.
|
|
*
|
|
* @param enabled Boolean indicating whether to enable or disable logging.
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public void setResourceResolutionLoggingEnabled(boolean enabled) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
nativeSetResourceResolutionLoggingEnabled(mObject, enabled);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the last resource resolution path logged.
|
|
*
|
|
* @return Formatted string containing last resource ID/name and steps taken to resolve final
|
|
* entry, including configuration and package names.
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public @Nullable String getLastResourceResolution() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetLastResourceResolution(mObject);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether the {@code resources.arsc} of any loaded apk assets is allocated in RAM
|
|
* (not mmapped).
|
|
*
|
|
* @hide
|
|
*/
|
|
public boolean containsAllocatedTable() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeContainsAllocatedTable(mObject);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
CharSequence getPooledStringForCookie(int cookie, int id) {
|
|
// Cookies map to ApkAssets starting at 1.
|
|
return getApkAssets()[cookie - 1].getStringFromPool(id);
|
|
}
|
|
|
|
/**
|
|
* Open an asset using ACCESS_STREAMING mode. This provides access to
|
|
* files that have been bundled with an application as assets -- that is,
|
|
* files placed in to the "assets" directory.
|
|
*
|
|
* @param fileName The name of the asset to open. This name can be hierarchical.
|
|
*
|
|
* @see #open(String, int)
|
|
* @see #list
|
|
*/
|
|
public @NonNull InputStream open(@NonNull String fileName) throws IOException {
|
|
return open(fileName, ACCESS_STREAMING);
|
|
}
|
|
|
|
/**
|
|
* Open an asset using an explicit access mode, returning an InputStream to
|
|
* read its contents. This provides access to files that have been bundled
|
|
* with an application as assets -- that is, files placed in to the
|
|
* "assets" directory.
|
|
*
|
|
* @param fileName The name of the asset to open. This name can be hierarchical.
|
|
* @param accessMode Desired access mode for retrieving the data.
|
|
*
|
|
* @see #ACCESS_UNKNOWN
|
|
* @see #ACCESS_STREAMING
|
|
* @see #ACCESS_RANDOM
|
|
* @see #ACCESS_BUFFER
|
|
* @see #open(String)
|
|
* @see #list
|
|
*/
|
|
public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
|
|
Objects.requireNonNull(fileName, "fileName");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
|
|
if (asset == 0) {
|
|
throw new FileNotFoundException("Asset file: " + fileName);
|
|
}
|
|
final AssetInputStream assetInputStream = new AssetInputStream(asset);
|
|
incRefsLocked(assetInputStream.hashCode());
|
|
return assetInputStream;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}.
|
|
* This provides access to files that have been bundled with an application as assets -- that
|
|
* is, files placed in to the "assets" directory.
|
|
*
|
|
* The asset must be uncompressed, or an exception will be thrown.
|
|
*
|
|
* @param fileName The name of the asset to open. This name can be hierarchical.
|
|
* @return An open AssetFileDescriptor.
|
|
*/
|
|
public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException {
|
|
Objects.requireNonNull(fileName, "fileName");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
|
|
if (pfd == null) {
|
|
throw new FileNotFoundException("Asset file: " + fileName);
|
|
}
|
|
return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a String array of all the assets at the given path.
|
|
*
|
|
* @param path A relative path within the assets, i.e., "docs/home.html".
|
|
*
|
|
* @return String[] Array of strings, one for each asset. These file
|
|
* names are relative to 'path'. You can open the file by
|
|
* concatenating 'path' and a name in the returned string (via
|
|
* File) and passing that to open().
|
|
*
|
|
* @see #open
|
|
*/
|
|
public @Nullable String[] list(@NonNull String path) throws IOException {
|
|
Objects.requireNonNull(path, "path");
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeList(mObject, path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset file as an asset using ACCESS_STREAMING mode. This
|
|
* provides direct access to all of the files included in an application
|
|
* package (not only its assets). Applications should not normally use
|
|
* this.
|
|
*
|
|
* @param fileName Name of the asset to retrieve.
|
|
*
|
|
* @see #open(String)
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException {
|
|
return openNonAsset(0, fileName, ACCESS_STREAMING);
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset file as an asset using a specific access mode. This
|
|
* provides direct access to all of the files included in an application
|
|
* package (not only its assets). Applications should not normally use
|
|
* this.
|
|
*
|
|
* @param fileName Name of the asset to retrieve.
|
|
* @param accessMode Desired access mode for retrieving the data.
|
|
*
|
|
* @see #ACCESS_UNKNOWN
|
|
* @see #ACCESS_STREAMING
|
|
* @see #ACCESS_RANDOM
|
|
* @see #ACCESS_BUFFER
|
|
* @see #open(String, int)
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode)
|
|
throws IOException {
|
|
return openNonAsset(0, fileName, accessMode);
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset in a specified package. Not for use by applications.
|
|
*
|
|
* @param cookie Identifier of the package to be opened.
|
|
* @param fileName Name of the asset to retrieve.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName)
|
|
throws IOException {
|
|
return openNonAsset(cookie, fileName, ACCESS_STREAMING);
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset in a specified package. Not for use by applications.
|
|
*
|
|
* @param cookie Identifier of the package to be opened.
|
|
* @param fileName Name of the asset to retrieve.
|
|
* @param accessMode Desired access mode for retrieving the data.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode)
|
|
throws IOException {
|
|
Objects.requireNonNull(fileName, "fileName");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
|
|
if (asset == 0) {
|
|
throw new FileNotFoundException("Asset absolute file: " + fileName);
|
|
}
|
|
final AssetInputStream assetInputStream = new AssetInputStream(asset);
|
|
incRefsLocked(assetInputStream.hashCode());
|
|
return assetInputStream;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
|
|
* This provides direct access to all of the files included in an application
|
|
* package (not only its assets). Applications should not normally use this.
|
|
*
|
|
* The asset must not be compressed, or an exception will be thrown.
|
|
*
|
|
* @param fileName Name of the asset to retrieve.
|
|
*/
|
|
public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName)
|
|
throws IOException {
|
|
return openNonAssetFd(0, fileName);
|
|
}
|
|
|
|
/**
|
|
* Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
|
|
* This provides direct access to all of the files included in an application
|
|
* package (not only its assets). Applications should not normally use this.
|
|
*
|
|
* The asset must not be compressed, or an exception will be thrown.
|
|
*
|
|
* @param cookie Identifier of the package to be opened.
|
|
* @param fileName Name of the asset to retrieve.
|
|
*/
|
|
public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName)
|
|
throws IOException {
|
|
Objects.requireNonNull(fileName, "fileName");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
final ParcelFileDescriptor pfd =
|
|
nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
|
|
if (pfd == null) {
|
|
throw new FileNotFoundException("Asset absolute file: " + fileName);
|
|
}
|
|
return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve a parser for a compiled XML file.
|
|
*
|
|
* @param fileName The name of the file to retrieve.
|
|
*/
|
|
public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
|
|
throws IOException {
|
|
return openXmlResourceParser(0, fileName);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a parser for a compiled XML file.
|
|
*
|
|
* @param cookie Identifier of the package to be opened.
|
|
* @param fileName The name of the file to retrieve.
|
|
*/
|
|
public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
|
|
throws IOException {
|
|
try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
|
|
XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
|
|
// If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve a non-asset as a compiled XML file. Not for use by applications.
|
|
*
|
|
* @param fileName The name of the file to retrieve.
|
|
* @hide
|
|
*/
|
|
@NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
|
|
return openXmlBlockAsset(0, fileName);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a non-asset as a compiled XML file. Not for use by
|
|
* applications.
|
|
*
|
|
* @param cookie Identifier of the package to be opened.
|
|
* @param fileName Name of the asset to retrieve.
|
|
* @hide
|
|
*/
|
|
@NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
|
|
Objects.requireNonNull(fileName, "fileName");
|
|
synchronized (this) {
|
|
ensureOpenLocked();
|
|
|
|
final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
|
|
if (xmlBlock == 0) {
|
|
throw new FileNotFoundException("Asset XML file: " + fileName);
|
|
}
|
|
final XmlBlock block = new XmlBlock(this, xmlBlock);
|
|
incRefsLocked(block.hashCode());
|
|
return block;
|
|
}
|
|
}
|
|
|
|
void xmlBlockGone(int id) {
|
|
synchronized (this) {
|
|
decRefsLocked(id);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
|
|
@Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
|
|
long outIndicesAddress) {
|
|
Objects.requireNonNull(inAttrs, "inAttrs");
|
|
synchronized (this) {
|
|
// Need to synchronize on AssetManager because we will be accessing
|
|
// the native implementation of AssetManager.
|
|
ensureValidLocked();
|
|
nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
|
|
parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
|
|
outIndicesAddress);
|
|
}
|
|
}
|
|
|
|
int[] getAttributeResolutionStack(long themePtr, @AttrRes int defStyleAttr,
|
|
@StyleRes int defStyleRes, @StyleRes int xmlStyle) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeAttributeResolutionStack(
|
|
mObject, themePtr, xmlStyle, defStyleAttr, defStyleRes);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
|
|
@Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
|
|
@NonNull int[] outIndices) {
|
|
Objects.requireNonNull(inAttrs, "inAttrs");
|
|
Objects.requireNonNull(outValues, "outValues");
|
|
Objects.requireNonNull(outIndices, "outIndices");
|
|
synchronized (this) {
|
|
// Need to synchronize on AssetManager because we will be accessing
|
|
// the native implementation of AssetManager.
|
|
ensureValidLocked();
|
|
return nativeResolveAttrs(mObject,
|
|
themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs,
|
|
@NonNull int[] outValues, @NonNull int[] outIndices) {
|
|
Objects.requireNonNull(parser, "parser");
|
|
Objects.requireNonNull(inAttrs, "inAttrs");
|
|
Objects.requireNonNull(outValues, "outValues");
|
|
Objects.requireNonNull(outIndices, "outIndices");
|
|
synchronized (this) {
|
|
// Need to synchronize on AssetManager because we will be accessing
|
|
// the native implementation of AssetManager.
|
|
ensureValidLocked();
|
|
return nativeRetrieveAttributes(
|
|
mObject, parser.mParseState, inAttrs, outValues, outIndices);
|
|
}
|
|
}
|
|
|
|
@UnsupportedAppUsage
|
|
long createTheme() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
long themePtr = nativeThemeCreate(mObject);
|
|
incRefsLocked(themePtr);
|
|
return themePtr;
|
|
}
|
|
}
|
|
|
|
void releaseTheme(long themePtr) {
|
|
synchronized (this) {
|
|
decRefsLocked(themePtr);
|
|
}
|
|
}
|
|
|
|
static long getThemeFreeFunction() {
|
|
return nativeGetThemeFreeFunction();
|
|
}
|
|
|
|
void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
|
|
synchronized (this) {
|
|
// Need to synchronize on AssetManager because we will be accessing
|
|
// the native implementation of AssetManager.
|
|
ensureValidLocked();
|
|
nativeThemeApplyStyle(mObject, themePtr, resId, force);
|
|
}
|
|
}
|
|
|
|
AssetManager rebaseTheme(long themePtr, @NonNull AssetManager newAssetManager,
|
|
@StyleRes int[] styleIds, @StyleRes boolean[] force, int count) {
|
|
// Exchange ownership of the theme with the new asset manager.
|
|
if (this != newAssetManager) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
decRefsLocked(themePtr);
|
|
}
|
|
synchronized (newAssetManager) {
|
|
newAssetManager.ensureValidLocked();
|
|
newAssetManager.incRefsLocked(themePtr);
|
|
}
|
|
}
|
|
|
|
try {
|
|
synchronized (newAssetManager) {
|
|
newAssetManager.ensureValidLocked();
|
|
nativeThemeRebase(newAssetManager.mObject, themePtr, styleIds, force, count);
|
|
}
|
|
} finally {
|
|
Reference.reachabilityFence(newAssetManager);
|
|
}
|
|
return newAssetManager;
|
|
}
|
|
|
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
|
void setThemeTo(long dstThemePtr, @NonNull AssetManager srcAssetManager, long srcThemePtr) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
synchronized (srcAssetManager) {
|
|
srcAssetManager.ensureValidLocked();
|
|
nativeThemeCopy(mObject, dstThemePtr, srcAssetManager.mObject, srcThemePtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
if (DEBUG_REFS && mNumRefs != 0) {
|
|
Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs);
|
|
if (mRefStacks != null) {
|
|
for (RuntimeException e : mRefStacks.values()) {
|
|
Log.w(TAG, "Reference from here", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
synchronized (this) {
|
|
if (mObject != 0) {
|
|
nativeDestroy(mObject);
|
|
mObject = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread
|
|
safe and it does not rely on AssetManager once it has been created. It completely owns the
|
|
underlying Asset. */
|
|
public final class AssetInputStream extends InputStream {
|
|
private long mAssetNativePtr;
|
|
private long mLength;
|
|
private long mMarkPos;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public final int getAssetInt() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public final long getNativeAsset() {
|
|
return mAssetNativePtr;
|
|
}
|
|
|
|
private AssetInputStream(long assetNativePtr) {
|
|
mAssetNativePtr = assetNativePtr;
|
|
mLength = nativeAssetGetLength(assetNativePtr);
|
|
}
|
|
|
|
@Override
|
|
public final int read() throws IOException {
|
|
ensureOpen();
|
|
return nativeAssetReadChar(mAssetNativePtr);
|
|
}
|
|
|
|
@Override
|
|
public final int read(@NonNull byte[] b) throws IOException {
|
|
ensureOpen();
|
|
Objects.requireNonNull(b, "b");
|
|
return nativeAssetRead(mAssetNativePtr, b, 0, b.length);
|
|
}
|
|
|
|
@Override
|
|
public final int read(@NonNull byte[] b, int off, int len) throws IOException {
|
|
ensureOpen();
|
|
Objects.requireNonNull(b, "b");
|
|
return nativeAssetRead(mAssetNativePtr, b, off, len);
|
|
}
|
|
|
|
@Override
|
|
public final long skip(long n) throws IOException {
|
|
ensureOpen();
|
|
long pos = nativeAssetSeek(mAssetNativePtr, 0, 0);
|
|
if ((pos + n) > mLength) {
|
|
n = mLength - pos;
|
|
}
|
|
if (n > 0) {
|
|
nativeAssetSeek(mAssetNativePtr, n, 0);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
@Override
|
|
public final int available() throws IOException {
|
|
ensureOpen();
|
|
final long len = nativeAssetGetRemainingLength(mAssetNativePtr);
|
|
return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
|
|
}
|
|
|
|
@Override
|
|
public final boolean markSupported() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public final void mark(int readlimit) {
|
|
ensureOpen();
|
|
mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0);
|
|
}
|
|
|
|
@Override
|
|
public final void reset() throws IOException {
|
|
ensureOpen();
|
|
nativeAssetSeek(mAssetNativePtr, mMarkPos, -1);
|
|
}
|
|
|
|
@Override
|
|
public final void close() throws IOException {
|
|
if (mAssetNativePtr != 0) {
|
|
nativeAssetDestroy(mAssetNativePtr);
|
|
mAssetNativePtr = 0;
|
|
|
|
synchronized (AssetManager.this) {
|
|
decRefsLocked(hashCode());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
close();
|
|
}
|
|
|
|
private void ensureOpen() {
|
|
if (mAssetNativePtr == 0) {
|
|
throw new IllegalStateException("AssetInputStream is closed");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine whether the state in this asset manager is up-to-date with
|
|
* the files on the filesystem. If false is returned, you need to
|
|
* instantiate a new AssetManager class to see the new data.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public boolean isUpToDate() {
|
|
synchronized (this) {
|
|
if (!mOpen) {
|
|
return false;
|
|
}
|
|
|
|
for (ApkAssets apkAssets : mApkAssets) {
|
|
if (!apkAssets.isUpToDate()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the locales that this asset manager contains data for.
|
|
*
|
|
* <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
|
|
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
|
|
* parsed using {@link Locale#forLanguageTag(String)}.
|
|
*
|
|
* <p>On SDK 20 (Android 4.4W: KitKat for watches) and below, locale strings
|
|
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
|
|
* and {@code CC} is a two letter country code.
|
|
*/
|
|
public String[] getLocales() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetLocales(mObject, false /*excludeSystem*/);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Same as getLocales(), except that locales that are only provided by the system (i.e. those
|
|
* present in framework-res.apk or its overlays) will not be listed.
|
|
*
|
|
* For example, if the "system" assets support English, French, and German, and the additional
|
|
* assets support Cherokee and French, getLocales() would return
|
|
* [Cherokee, English, French, German], while getNonSystemLocales() would return
|
|
* [Cherokee, French].
|
|
* @hide
|
|
*/
|
|
public String[] getNonSystemLocales() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetLocales(mObject, true /*excludeSystem*/);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
Configuration[] getSizeConfigurations() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetSizeConfigurations(mObject);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
Configuration[] getSizeAndUiModeConfigurations() {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetSizeAndUiModeConfigurations(mObject);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the configuration used when retrieving resources. Not for use by
|
|
* applications.
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation,
|
|
int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
|
|
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
|
|
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
|
|
int majorVersion) {
|
|
if (locale != null) {
|
|
setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
|
|
density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
|
|
smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
|
|
colorMode, grammaticalGender, majorVersion);
|
|
} else {
|
|
setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
|
|
keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
|
|
smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
|
|
colorMode, grammaticalGender, majorVersion);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change the configuration used when retrieving resources. Not for use by
|
|
* applications.
|
|
* @hide
|
|
*/
|
|
public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
|
|
int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
|
|
int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
|
|
int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
|
|
int grammaticalGender, int majorVersion) {
|
|
setConfigurationInternal(mcc, mnc, defaultLocale, locales, orientation,
|
|
touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
|
|
screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
|
|
screenLayout, uiMode, colorMode, grammaticalGender, majorVersion, false);
|
|
}
|
|
|
|
/**
|
|
* Change the configuration used when retrieving resources, and potentially force a refresh of
|
|
* the state. Not for use by applications.
|
|
* @hide
|
|
*/
|
|
void setConfigurationInternal(int mcc, int mnc, String defaultLocale, String[] locales,
|
|
int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
|
|
int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
|
|
int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
|
|
int grammaticalGender, int majorVersion, boolean forceRefresh) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
|
|
touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
|
|
screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
|
|
screenLayout, uiMode, colorMode, grammaticalGender, majorVersion,
|
|
forceRefresh);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public SparseArray<String> getAssignedPackageIdentifiers() {
|
|
return getAssignedPackageIdentifiers(true, true);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public SparseArray<String> getAssignedPackageIdentifiers(boolean includeOverlays,
|
|
boolean includeLoaders) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetAssignedPackageIdentifiers(mObject, includeOverlays, includeLoaders);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@GuardedBy("this")
|
|
public @Nullable Map<String, String> getOverlayableMap(String packageName) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetOverlayableMap(mObject, packageName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
@GuardedBy("this")
|
|
public @Nullable String getOverlayablesToString(String packageName) {
|
|
synchronized (this) {
|
|
ensureValidLocked();
|
|
return nativeGetOverlayablesToString(mObject, packageName);
|
|
}
|
|
}
|
|
|
|
@GuardedBy("this")
|
|
private void incRefsLocked(long id) {
|
|
if (DEBUG_REFS) {
|
|
if (mRefStacks == null) {
|
|
mRefStacks = new HashMap<>();
|
|
}
|
|
RuntimeException ex = new RuntimeException();
|
|
ex.fillInStackTrace();
|
|
mRefStacks.put(id, ex);
|
|
}
|
|
mNumRefs++;
|
|
}
|
|
|
|
@GuardedBy("this")
|
|
private void decRefsLocked(long id) {
|
|
if (DEBUG_REFS && mRefStacks != null) {
|
|
mRefStacks.remove(id);
|
|
}
|
|
mNumRefs--;
|
|
if (mNumRefs == 0 && mObject != 0) {
|
|
nativeDestroy(mObject);
|
|
mObject = 0;
|
|
mApkAssets = sEmptyApkAssets;
|
|
}
|
|
}
|
|
|
|
synchronized void dump(PrintWriter pw, String prefix) {
|
|
pw.println(prefix + "class=" + getClass());
|
|
pw.println(prefix + "apkAssets=");
|
|
for (int i = 0; i < mApkAssets.length; i++) {
|
|
pw.println(prefix + i);
|
|
mApkAssets[i].dump(pw, prefix + " ");
|
|
}
|
|
}
|
|
|
|
// AssetManager setup native methods.
|
|
private static native long nativeCreate();
|
|
private static native void nativeDestroy(long ptr);
|
|
private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
|
|
boolean invalidateCaches, boolean preset);
|
|
private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
|
|
@Nullable String defaultLocale, @NonNull String[] locales, int orientation,
|
|
int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
|
|
int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
|
|
int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
|
|
int majorVersion, boolean forceRefresh);
|
|
private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
|
|
long ptr, boolean includeOverlays, boolean includeLoaders);
|
|
|
|
// File native methods.
|
|
private static native boolean nativeContainsAllocatedTable(long ptr);
|
|
private static native @Nullable String[] nativeList(long ptr, @NonNull String path)
|
|
throws IOException;
|
|
private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
|
|
private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr,
|
|
@NonNull String fileName, long[] outOffsets) throws IOException;
|
|
private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName,
|
|
int accessMode);
|
|
private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
|
|
@NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
|
|
private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
|
|
private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
|
|
@NonNull FileDescriptor fileDescriptor);
|
|
|
|
// Primitive resource native methods.
|
|
private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
|
|
@NonNull TypedValue outValue, boolean resolveReferences);
|
|
private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId,
|
|
@NonNull TypedValue outValue);
|
|
|
|
private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr,
|
|
@StyleRes int resId);
|
|
private static native @Nullable String[] nativeGetResourceStringArray(long ptr,
|
|
@ArrayRes int resId);
|
|
private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr,
|
|
@ArrayRes int resId);
|
|
private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId);
|
|
private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId);
|
|
private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId,
|
|
@NonNull int[] outValues);
|
|
|
|
// Resource name/ID native methods.
|
|
private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name,
|
|
@Nullable String defType, @Nullable String defPackage);
|
|
private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid);
|
|
private static native @Nullable String nativeGetResourcePackageName(long ptr,
|
|
@AnyRes int resid);
|
|
private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid);
|
|
private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
|
|
private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
|
|
private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
|
|
private static native @Nullable Configuration[] nativeGetSizeAndUiModeConfigurations(long ptr);
|
|
private static native void nativeSetResourceResolutionLoggingEnabled(long ptr, boolean enabled);
|
|
private static native @Nullable String nativeGetLastResourceResolution(long ptr);
|
|
|
|
// Style attribute retrieval native methods.
|
|
private static native int[] nativeAttributeResolutionStack(long ptr, long themePtr,
|
|
@StyleRes int xmlStyleRes, @AttrRes int defStyleAttr, @StyleRes int defStyleRes);
|
|
private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
|
|
@StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs,
|
|
long outValuesAddress, long outIndicesAddress);
|
|
private static native boolean nativeResolveAttrs(long ptr, long themePtr,
|
|
@AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues,
|
|
@NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
|
|
private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr,
|
|
@NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
|
|
|
|
// Theme related native methods
|
|
private static native long nativeThemeCreate(long ptr);
|
|
private static native long nativeGetThemeFreeFunction();
|
|
private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
|
|
boolean force);
|
|
private static native void nativeThemeRebase(long ptr, long themePtr, @NonNull int[] styleIds,
|
|
@NonNull boolean[] force, int styleSize);
|
|
private static native void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr,
|
|
long srcAssetManagerPtr, long srcThemePtr);
|
|
private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
|
|
@AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
|
|
private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
|
|
String prefix);
|
|
static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
|
|
@StyleRes
|
|
private static native int nativeGetParentThemeIdentifier(long ptr, @StyleRes int styleId);
|
|
|
|
// AssetInputStream related native methods.
|
|
private static native void nativeAssetDestroy(long assetPtr);
|
|
private static native int nativeAssetReadChar(long assetPtr);
|
|
private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len);
|
|
private static native long nativeAssetSeek(long assetPtr, long offset, int whence);
|
|
private static native long nativeAssetGetLength(long assetPtr);
|
|
private static native long nativeAssetGetRemainingLength(long assetPtr);
|
|
|
|
private static native @Nullable Map nativeGetOverlayableMap(long ptr,
|
|
@NonNull String packageName);
|
|
private static native @Nullable String nativeGetOverlayablesToString(long ptr,
|
|
@NonNull String packageName);
|
|
|
|
// Global debug native methods.
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static native int getGlobalAssetCount();
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public static native String getAssetAllocations();
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@UnsupportedAppUsage
|
|
public static native int getGlobalAssetManagerCount();
|
|
}
|