1892 lines
80 KiB
Java
1892 lines
80 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2013 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.app;
|
||
|
|
||
|
import static android.app.ActivityThread.DEBUG_CONFIGURATION;
|
||
|
import static android.view.Display.DEFAULT_DISPLAY;
|
||
|
import static android.view.Display.INVALID_DISPLAY;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.pm.ActivityInfo;
|
||
|
import android.content.pm.ApplicationInfo;
|
||
|
import android.content.res.ApkAssets;
|
||
|
import android.content.res.AssetManager;
|
||
|
import android.content.res.CompatResources;
|
||
|
import android.content.res.CompatibilityInfo;
|
||
|
import android.content.res.Configuration;
|
||
|
import android.content.res.Resources;
|
||
|
import android.content.res.ResourcesImpl;
|
||
|
import android.content.res.ResourcesKey;
|
||
|
import android.content.res.loader.ResourcesLoader;
|
||
|
import android.hardware.display.DisplayManagerGlobal;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.LocaleList;
|
||
|
import android.os.Process;
|
||
|
import android.os.Trace;
|
||
|
import android.util.ArrayMap;
|
||
|
import android.util.ArraySet;
|
||
|
import android.util.DisplayMetrics;
|
||
|
import android.util.IndentingPrintWriter;
|
||
|
import android.util.Log;
|
||
|
import android.util.Pair;
|
||
|
import android.util.Slog;
|
||
|
import android.view.Display;
|
||
|
import android.view.DisplayAdjustments;
|
||
|
import android.view.DisplayInfo;
|
||
|
import android.window.WindowContext;
|
||
|
|
||
|
import com.android.internal.annotations.VisibleForTesting;
|
||
|
import com.android.internal.util.ArrayUtils;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.lang.ref.Reference;
|
||
|
import java.lang.ref.ReferenceQueue;
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collection;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
import java.util.WeakHashMap;
|
||
|
import java.util.function.Function;
|
||
|
|
||
|
/** @hide */
|
||
|
public class ResourcesManager {
|
||
|
static final String TAG = "ResourcesManager";
|
||
|
private static final boolean DEBUG = false;
|
||
|
|
||
|
private static ResourcesManager sResourcesManager;
|
||
|
|
||
|
/**
|
||
|
* Internal lock object
|
||
|
*/
|
||
|
private final Object mLock = new Object();
|
||
|
|
||
|
/**
|
||
|
* The global compatibility settings.
|
||
|
*/
|
||
|
private CompatibilityInfo mResCompatibilityInfo;
|
||
|
|
||
|
/**
|
||
|
* The global configuration upon which all Resources are based. Multi-window Resources
|
||
|
* apply their overrides to this configuration.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private final Configuration mResConfiguration = new Configuration();
|
||
|
|
||
|
/**
|
||
|
* The display upon which all Resources are based. Activity, window token, and display context
|
||
|
* resources apply their overrides to this display id.
|
||
|
*/
|
||
|
private int mResDisplayId = DEFAULT_DISPLAY;
|
||
|
|
||
|
/**
|
||
|
* ApplicationInfo changes that need to be applied to Resources when the next configuration
|
||
|
* change occurs.
|
||
|
*/
|
||
|
private ArrayList<Pair<String[], ApplicationInfo>> mPendingAppInfoUpdates;
|
||
|
|
||
|
/**
|
||
|
* A mapping of ResourceImpls and their configurations. These are heavy weight objects
|
||
|
* which should be reused as much as possible.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
|
||
|
new ArrayMap<>();
|
||
|
|
||
|
/**
|
||
|
* A list of Resource references that can be reused.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
|
||
|
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
|
||
|
|
||
|
/**
|
||
|
* A list of Resources references for all Resources instances created through Resources public
|
||
|
* constructor, only system Resources created by the private constructor are excluded.
|
||
|
* This addition is necessary due to certain Application Resources created by constructor
|
||
|
* directly which are not managed by ResourcesManager, hence we require a comprehensive
|
||
|
* collection of all Resources references to help with asset paths appending tasks when shared
|
||
|
* libraries are registered.
|
||
|
*/
|
||
|
private final ArrayList<WeakReference<Resources>> mAllResourceReferences = new ArrayList<>();
|
||
|
private final ReferenceQueue<Resources> mAllResourceReferencesQueue = new ReferenceQueue<>();
|
||
|
|
||
|
/**
|
||
|
* The localeConfig of the app.
|
||
|
*/
|
||
|
private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
|
||
|
|
||
|
private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
|
||
|
new ArrayMap<>();
|
||
|
|
||
|
/**
|
||
|
* The internal function to register the resources paths of a package (e.g. a shared library).
|
||
|
* This will collect the package resources' paths from its ApplicationInfo and add them to all
|
||
|
* existing and future contexts while the application is running.
|
||
|
*/
|
||
|
public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
|
||
|
SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
|
||
|
appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
|
||
|
appInfo.resourceDirs, appInfo.overlayPaths);
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
if (mSharedLibAssetsMap.containsKey(uniqueId)) {
|
||
|
Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
|
||
|
+ " has already been registered, this is a no-op.");
|
||
|
return;
|
||
|
}
|
||
|
mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
|
||
|
appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
|
||
|
Slog.v(TAG, "The following resources' paths have been added: "
|
||
|
+ Arrays.toString(sharedLibAssets.getAllAssetPaths()));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static class ApkKey {
|
||
|
public final String path;
|
||
|
public final boolean sharedLib;
|
||
|
public final boolean overlay;
|
||
|
|
||
|
ApkKey(String path, boolean sharedLib, boolean overlay) {
|
||
|
this.path = path;
|
||
|
this.sharedLib = sharedLib;
|
||
|
this.overlay = overlay;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
int result = 1;
|
||
|
result = 31 * result + this.path.hashCode();
|
||
|
result = 31 * result + Boolean.hashCode(this.sharedLib);
|
||
|
result = 31 * result + Boolean.hashCode(this.overlay);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(@Nullable Object obj) {
|
||
|
if (!(obj instanceof ApkKey)) {
|
||
|
return false;
|
||
|
}
|
||
|
ApkKey other = (ApkKey) obj;
|
||
|
return this.path.equals(other.path) && this.sharedLib == other.sharedLib
|
||
|
&& this.overlay == other.overlay;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
|
||
|
* instance is alive and reachable.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
protected class ApkAssetsSupplier {
|
||
|
final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
|
||
|
|
||
|
/**
|
||
|
* Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
|
||
|
* within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
|
||
|
* cache.
|
||
|
*/
|
||
|
ApkAssets load(final ApkKey apkKey) throws IOException {
|
||
|
ApkAssets apkAssets = mLocalCache.get(apkKey);
|
||
|
if (apkAssets == null) {
|
||
|
apkAssets = loadApkAssets(apkKey);
|
||
|
mLocalCache.put(apkKey, apkAssets);
|
||
|
}
|
||
|
return apkAssets;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The ApkAssets that are being referenced in the wild that we can reuse.
|
||
|
* Used as a lock for itself as well.
|
||
|
*/
|
||
|
private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
|
||
|
|
||
|
/**
|
||
|
* Class containing the base configuration override and set of resources associated with an
|
||
|
* {@link Activity} or a {@link WindowContext}.
|
||
|
*/
|
||
|
private static class ActivityResources {
|
||
|
/**
|
||
|
* Override config to apply to all resources associated with the token this instance is
|
||
|
* based on.
|
||
|
*
|
||
|
* @see #activityResources
|
||
|
* @see #getResources(IBinder, String, String[], String[], String[], String[], Integer,
|
||
|
* Configuration, CompatibilityInfo, ClassLoader, List)
|
||
|
*/
|
||
|
public final Configuration overrideConfig = new Configuration();
|
||
|
|
||
|
/**
|
||
|
* The display to apply to all resources associated with the token this instance is based
|
||
|
* on.
|
||
|
*/
|
||
|
public int overrideDisplayId;
|
||
|
|
||
|
/** List of {@link ActivityResource} associated with the token this instance is based on. */
|
||
|
public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
|
||
|
|
||
|
public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private ActivityResources() {}
|
||
|
|
||
|
/** Returns the number of live resource references within {@code activityResources}. */
|
||
|
public int countLiveReferences() {
|
||
|
int count = 0;
|
||
|
for (int i = 0; i < activityResources.size(); i++) {
|
||
|
WeakReference<Resources> resources = activityResources.get(i).resources;
|
||
|
if (resources != null && resources.get() != null) {
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Contains a resource derived from an {@link Activity} or {@link WindowContext} and information
|
||
|
* about how this resource expects its configuration to differ from the token's.
|
||
|
*
|
||
|
* @see ActivityResources
|
||
|
*/
|
||
|
// TODO: Ideally this class should be called something token related, like TokenBasedResource.
|
||
|
private static class ActivityResource {
|
||
|
/**
|
||
|
* The override configuration applied on top of the token's override config for this
|
||
|
* resource.
|
||
|
*/
|
||
|
public final Configuration overrideConfig = new Configuration();
|
||
|
|
||
|
/**
|
||
|
* If non-null this resource expects its configuration to override the display from the
|
||
|
* token's configuration.
|
||
|
*
|
||
|
* @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
|
||
|
*/
|
||
|
@Nullable
|
||
|
public Integer overrideDisplayId;
|
||
|
|
||
|
@Nullable
|
||
|
public WeakReference<Resources> resources;
|
||
|
|
||
|
private ActivityResource() {}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Each Activity or WindowToken may has a base override configuration that is applied to each
|
||
|
* Resources object, which in turn may have their own override configuration specified.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
|
||
|
new WeakHashMap<>();
|
||
|
|
||
|
/**
|
||
|
* Callback implementation for handling updates to Resources objects.
|
||
|
*/
|
||
|
private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
|
||
|
|
||
|
/**
|
||
|
* The set of APK paths belonging to this process. This is used to disable incremental
|
||
|
* installation crash protections on these APKs so the app either behaves as expects or crashes.
|
||
|
*/
|
||
|
private final ArraySet<String> mApplicationOwnedApks = new ArraySet<>();
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
public ResourcesManager() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
|
||
|
* instance.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
@VisibleForTesting
|
||
|
public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
|
||
|
synchronized (ResourcesManager.class) {
|
||
|
ResourcesManager oldResourceManager = sResourcesManager;
|
||
|
sResourcesManager = resourcesManager;
|
||
|
return oldResourceManager;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
public static ResourcesManager getInstance() {
|
||
|
synchronized (ResourcesManager.class) {
|
||
|
if (sResourcesManager == null) {
|
||
|
sResourcesManager = new ResourcesManager();
|
||
|
}
|
||
|
return sResourcesManager;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invalidate and destroy any resources that reference content under the
|
||
|
* given filesystem path. Typically used when unmounting a storage device to
|
||
|
* try as hard as possible to release any open FDs.
|
||
|
*/
|
||
|
public void invalidatePath(String path) {
|
||
|
final List<ResourcesImpl> implsToFlush = new ArrayList<>();
|
||
|
synchronized (mLock) {
|
||
|
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
|
||
|
final ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
if (key.isPathReferenced(path)) {
|
||
|
ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
|
||
|
if (resImpl != null) {
|
||
|
implsToFlush.add(resImpl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (int i = 0; i < implsToFlush.size(); i++) {
|
||
|
implsToFlush.get(i).flushLayoutCache();
|
||
|
}
|
||
|
final List<ApkAssets> assetsToClose = new ArrayList<>();
|
||
|
synchronized (mCachedApkAssets) {
|
||
|
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
|
||
|
final ApkKey key = mCachedApkAssets.keyAt(i);
|
||
|
if (key.path.equals(path)) {
|
||
|
final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
|
||
|
final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
|
||
|
if (apkAssets != null) {
|
||
|
assetsToClose.add(apkAssets);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (int i = 0; i < assetsToClose.size(); i++) {
|
||
|
assetsToClose.get(i).close();
|
||
|
}
|
||
|
Log.i(TAG,
|
||
|
"Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
|
||
|
}
|
||
|
|
||
|
public Configuration getConfiguration() {
|
||
|
return mResConfiguration;
|
||
|
}
|
||
|
|
||
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||
|
public DisplayMetrics getDisplayMetrics() {
|
||
|
return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Protected so that tests can override and returns something a fixed value.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
|
||
|
final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
|
||
|
final DisplayMetrics dm = new DisplayMetrics();
|
||
|
final DisplayInfo displayInfo = displayManagerGlobal != null
|
||
|
? displayManagerGlobal.getDisplayInfo(displayId) : null;
|
||
|
if (displayInfo != null) {
|
||
|
displayInfo.getAppMetrics(dm, da);
|
||
|
} else {
|
||
|
dm.setToDefaults();
|
||
|
}
|
||
|
return dm;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Like getDisplayMetrics, but will adjust the result based on the display information in
|
||
|
* config. This is used to make sure that the global configuration matches the activity's
|
||
|
* apparent display.
|
||
|
*/
|
||
|
private DisplayMetrics getDisplayMetrics(Configuration config) {
|
||
|
final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
|
||
|
final DisplayMetrics dm = new DisplayMetrics();
|
||
|
final DisplayInfo displayInfo = displayManagerGlobal != null
|
||
|
? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null;
|
||
|
if (displayInfo != null) {
|
||
|
displayInfo.getAppMetrics(dm,
|
||
|
DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config);
|
||
|
} else {
|
||
|
dm.setToDefaults();
|
||
|
}
|
||
|
return dm;
|
||
|
}
|
||
|
|
||
|
private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
|
||
|
@NonNull Configuration config) {
|
||
|
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
|
||
|
config.densityDpi = dm.densityDpi;
|
||
|
config.screenWidthDp = (int) (dm.widthPixels / dm.density + 0.5f);
|
||
|
config.screenHeightDp = (int) (dm.heightPixels / dm.density + 0.5f);
|
||
|
int sl = Configuration.resetScreenLayout(config.screenLayout);
|
||
|
if (dm.widthPixels > dm.heightPixels) {
|
||
|
config.orientation = Configuration.ORIENTATION_LANDSCAPE;
|
||
|
config.screenLayout = Configuration.reduceScreenLayout(sl,
|
||
|
config.screenWidthDp, config.screenHeightDp);
|
||
|
} else {
|
||
|
config.orientation = Configuration.ORIENTATION_PORTRAIT;
|
||
|
config.screenLayout = Configuration.reduceScreenLayout(sl,
|
||
|
config.screenHeightDp, config.screenWidthDp);
|
||
|
}
|
||
|
config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp);
|
||
|
config.compatScreenWidthDp = config.screenWidthDp;
|
||
|
config.compatScreenHeightDp = config.screenHeightDp;
|
||
|
config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
|
||
|
}
|
||
|
|
||
|
public boolean applyCompatConfiguration(int displayDensity,
|
||
|
@NonNull Configuration compatConfiguration) {
|
||
|
synchronized (mLock) {
|
||
|
if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
|
||
|
mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an adjusted {@link Display} object based on the inputs or null if display isn't
|
||
|
* available.
|
||
|
*
|
||
|
* @param displayId display Id.
|
||
|
* @param resources The {@link Resources} backing the display adjustments.
|
||
|
*/
|
||
|
public Display getAdjustedDisplay(final int displayId, Resources resources) {
|
||
|
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
|
||
|
if (dm == null) {
|
||
|
// may be null early in system startup
|
||
|
return null;
|
||
|
}
|
||
|
return dm.getCompatibleDisplay(displayId, resources);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the set of APKs owned by the application running in this process.
|
||
|
*/
|
||
|
public void initializeApplicationPaths(@NonNull String sourceDir,
|
||
|
@Nullable String[] splitDirs) {
|
||
|
synchronized (mLock) {
|
||
|
if (mApplicationOwnedApks.isEmpty()) {
|
||
|
addApplicationPathsLocked(sourceDir, splitDirs);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates the set of APKs owned by the application running in this process.
|
||
|
*
|
||
|
* This method only appends to the set of APKs owned by this process because the previous APKs
|
||
|
* paths still belong to the application running in this process.
|
||
|
*/
|
||
|
private void addApplicationPathsLocked(@NonNull String sourceDir,
|
||
|
@Nullable String[] splitDirs) {
|
||
|
mApplicationOwnedApks.add(sourceDir);
|
||
|
if (splitDirs != null) {
|
||
|
mApplicationOwnedApks.addAll(Arrays.asList(splitDirs));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static String overlayPathToIdmapPath(String path) {
|
||
|
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
|
||
|
}
|
||
|
|
||
|
private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
|
||
|
ApkAssets apkAssets;
|
||
|
|
||
|
// Optimistically check if this ApkAssets exists somewhere else.
|
||
|
final WeakReference<ApkAssets> apkAssetsRef;
|
||
|
synchronized (mCachedApkAssets) {
|
||
|
apkAssetsRef = mCachedApkAssets.get(key);
|
||
|
}
|
||
|
if (apkAssetsRef != null) {
|
||
|
apkAssets = apkAssetsRef.get();
|
||
|
if (apkAssets != null && apkAssets.isUpToDate()) {
|
||
|
return apkAssets;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int flags = 0;
|
||
|
if (key.sharedLib) {
|
||
|
flags |= ApkAssets.PROPERTY_DYNAMIC;
|
||
|
}
|
||
|
if (mApplicationOwnedApks.contains(key.path)) {
|
||
|
flags |= ApkAssets.PROPERTY_DISABLE_INCREMENTAL_HARDENING;
|
||
|
}
|
||
|
if (key.overlay) {
|
||
|
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), flags);
|
||
|
} else {
|
||
|
apkAssets = ApkAssets.loadFromPath(key.path, flags);
|
||
|
}
|
||
|
|
||
|
synchronized (mCachedApkAssets) {
|
||
|
mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
|
||
|
}
|
||
|
|
||
|
return apkAssets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieves a list of apk keys representing the ApkAssets that should be loaded for
|
||
|
* AssetManagers mapped to the {@param key}.
|
||
|
*/
|
||
|
private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
|
||
|
final ArrayList<ApkKey> apkKeys = new ArrayList<>();
|
||
|
|
||
|
// resDir can be null if the 'android' package is creating a new Resources object.
|
||
|
// This is fine, since each AssetManager automatically loads the 'android' package
|
||
|
// already.
|
||
|
if (key.mResDir != null) {
|
||
|
apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
|
||
|
}
|
||
|
|
||
|
if (key.mSplitResDirs != null) {
|
||
|
for (final String splitResDir : key.mSplitResDirs) {
|
||
|
apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (key.mLibDirs != null) {
|
||
|
for (final String libDir : key.mLibDirs) {
|
||
|
// Avoid opening files we know do not have resources, like code-only .jar files.
|
||
|
if (libDir.endsWith(".apk")) {
|
||
|
apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (key.mOverlayPaths != null) {
|
||
|
for (final String idmapPath : key.mOverlayPaths) {
|
||
|
apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return apkKeys;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an AssetManager from the paths within the ResourcesKey.
|
||
|
*
|
||
|
* This can be overridden in tests so as to avoid creating a real AssetManager with
|
||
|
* real APK paths.
|
||
|
* @param key The key containing the resource paths to add to the AssetManager.
|
||
|
* @return a new AssetManager.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
@UnsupportedAppUsage
|
||
|
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
|
||
|
return createAssetManager(key, /* apkSupplier */ null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
|
||
|
* from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
|
||
|
* {@link #loadApkAssets(ApkKey)}.
|
||
|
*/
|
||
|
|
||
|
@VisibleForTesting
|
||
|
@UnsupportedAppUsage
|
||
|
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
|
||
|
@Nullable ApkAssetsSupplier apkSupplier) {
|
||
|
final AssetManager.Builder builder = new AssetManager.Builder().setNoInit();
|
||
|
|
||
|
final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
|
||
|
for (int i = 0, n = apkKeys.size(); i < n; i++) {
|
||
|
final ApkKey apkKey = apkKeys.get(i);
|
||
|
try {
|
||
|
builder.addApkAssets(
|
||
|
(apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
|
||
|
} catch (IOException e) {
|
||
|
if (apkKey.overlay) {
|
||
|
Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
|
||
|
} else if (apkKey.sharedLib) {
|
||
|
Log.w(TAG, String.format(
|
||
|
"asset path '%s' does not exist or contains no resources",
|
||
|
apkKey.path), e);
|
||
|
} else {
|
||
|
Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (key.mLoaders != null) {
|
||
|
for (final ResourcesLoader loader : key.mLoaders) {
|
||
|
builder.addLoader(loader);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return builder.build();
|
||
|
}
|
||
|
|
||
|
private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
|
||
|
int count = 0;
|
||
|
for (WeakReference<T> ref : collection) {
|
||
|
final T value = ref != null ? ref.get() : null;
|
||
|
if (value != null) {
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public void dump(String prefix, PrintWriter printWriter) {
|
||
|
final int references;
|
||
|
final int resImpls;
|
||
|
synchronized (mLock) {
|
||
|
int refs = countLiveReferences(mResourceReferences);
|
||
|
for (ActivityResources activityResources : mActivityResourceReferences.values()) {
|
||
|
refs += activityResources.countLiveReferences();
|
||
|
}
|
||
|
references = refs;
|
||
|
resImpls = countLiveReferences(mResourceImpls.values());
|
||
|
}
|
||
|
final int liveAssets;
|
||
|
synchronized (mCachedApkAssets) {
|
||
|
liveAssets = countLiveReferences(mCachedApkAssets.values());
|
||
|
}
|
||
|
|
||
|
final var pw = new IndentingPrintWriter(printWriter, " ");
|
||
|
for (int i = 0; i < prefix.length() / 2; i++) {
|
||
|
pw.increaseIndent();
|
||
|
}
|
||
|
pw.println("ResourcesManager:");
|
||
|
pw.increaseIndent();
|
||
|
pw.print("total apks: ");
|
||
|
pw.println(liveAssets);
|
||
|
pw.print("resources: ");
|
||
|
pw.println(references);
|
||
|
pw.print("resource impls: ");
|
||
|
pw.println(resImpls);
|
||
|
}
|
||
|
|
||
|
private Configuration generateConfig(@NonNull ResourcesKey key) {
|
||
|
Configuration config;
|
||
|
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
|
||
|
if (hasOverrideConfig) {
|
||
|
config = new Configuration(getConfiguration());
|
||
|
config.updateFrom(key.mOverrideConfiguration);
|
||
|
if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
|
||
|
} else {
|
||
|
config = getConfiguration();
|
||
|
}
|
||
|
return config;
|
||
|
}
|
||
|
|
||
|
private int generateDisplayId(@NonNull ResourcesKey key) {
|
||
|
return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId;
|
||
|
}
|
||
|
|
||
|
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
|
||
|
@Nullable ApkAssetsSupplier apkSupplier) {
|
||
|
final AssetManager assets = createAssetManager(key, apkSupplier);
|
||
|
if (assets == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
|
||
|
daj.setCompatibilityInfo(key.mCompatInfo);
|
||
|
|
||
|
final Configuration config = generateConfig(key);
|
||
|
final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
|
||
|
final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
|
||
|
|
||
|
if (DEBUG) {
|
||
|
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
|
||
|
}
|
||
|
return impl;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds a cached ResourcesImpl object that matches the given ResourcesKey.
|
||
|
*
|
||
|
* @param key The key to match.
|
||
|
* @return a ResourcesImpl if the key matches a cache entry, null otherwise.
|
||
|
*/
|
||
|
private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
|
||
|
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
|
||
|
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
|
||
|
if (impl != null && impl.getAssets().isUpToDate()) {
|
||
|
return impl;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
|
||
|
* creates a new one and caches it for future use.
|
||
|
* @param key The key to match.
|
||
|
* @return a ResourcesImpl object matching the key.
|
||
|
*/
|
||
|
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
|
||
|
@NonNull ResourcesKey key) {
|
||
|
return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
|
||
|
* load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
|
||
|
*/
|
||
|
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
|
||
|
@NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
|
||
|
ResourcesImpl impl = findResourcesImplForKeyLocked(key);
|
||
|
// ResourcesImpl also need to be recreated if its shared library count is not up-to-date.
|
||
|
if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) {
|
||
|
impl = createResourcesImpl(key, apkSupplier);
|
||
|
if (impl != null) {
|
||
|
mResourceImpls.put(key, new WeakReference<>(impl));
|
||
|
}
|
||
|
}
|
||
|
return impl;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Find the ResourcesKey that this ResourcesImpl object is associated with.
|
||
|
* @return the ResourcesKey or null if none was found.
|
||
|
*/
|
||
|
private @Nullable ResourcesKey findKeyForResourceImplLocked(
|
||
|
@NonNull ResourcesImpl resourceImpl) {
|
||
|
int refCount = mResourceImpls.size();
|
||
|
for (int i = 0; i < refCount; i++) {
|
||
|
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
|
||
|
if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) {
|
||
|
return mResourceImpls.keyAt(i);
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if activity resources have same override config as the provided on.
|
||
|
* @param activityToken The Activity that resources should be associated with.
|
||
|
* @param overrideConfig The override configuration to be checked for equality with.
|
||
|
* @return true if activity resources override config matches the provided one or they are both
|
||
|
* null, false otherwise.
|
||
|
*/
|
||
|
public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
|
||
|
@Nullable Configuration overrideConfig) {
|
||
|
synchronized (mLock) {
|
||
|
final ActivityResources activityResources
|
||
|
= activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
|
||
|
if (activityResources == null) {
|
||
|
return overrideConfig == null;
|
||
|
} else {
|
||
|
// The two configurations must either be equal or publicly equivalent to be
|
||
|
// considered the same.
|
||
|
return Objects.equals(activityResources.overrideConfig, overrideConfig)
|
||
|
|| (overrideConfig != null && activityResources.overrideConfig != null
|
||
|
&& 0 == overrideConfig.diffPublicOnly(
|
||
|
activityResources.overrideConfig));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ActivityResources getOrCreateActivityResourcesStructLocked(
|
||
|
@NonNull IBinder activityToken) {
|
||
|
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
|
||
|
if (activityResources == null) {
|
||
|
activityResources = new ActivityResources();
|
||
|
mActivityResourceReferences.put(activityToken, activityResources);
|
||
|
}
|
||
|
return activityResources;
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
|
||
|
@NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
|
||
|
ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
|
||
|
targetActivityToken);
|
||
|
|
||
|
final int size = activityResources.activityResources.size();
|
||
|
for (int index = 0; index < size; index++) {
|
||
|
ActivityResource activityResource = activityResources.activityResources.get(index);
|
||
|
Resources resources = activityResource.resources.get();
|
||
|
ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
|
||
|
resources.getImpl());
|
||
|
|
||
|
if (key != null
|
||
|
&& Objects.equals(resources.getClassLoader(), targetClassLoader)
|
||
|
&& Objects.equals(key, targetKey)) {
|
||
|
return resources;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@NonNull
|
||
|
private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
|
||
|
@NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
|
||
|
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
|
||
|
@NonNull CompatibilityInfo compatInfo) {
|
||
|
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
|
||
|
activityToken);
|
||
|
cleanupReferences(activityResources.activityResources,
|
||
|
activityResources.activityResourcesQueue,
|
||
|
(r) -> r.resources);
|
||
|
|
||
|
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
|
||
|
: new Resources(classLoader);
|
||
|
resources.setImpl(impl);
|
||
|
resources.setCallbacks(mUpdateCallbacks);
|
||
|
|
||
|
ActivityResource activityResource = new ActivityResource();
|
||
|
activityResource.resources = new WeakReference<>(resources,
|
||
|
activityResources.activityResourcesQueue);
|
||
|
activityResource.overrideConfig.setTo(initialOverrideConfig);
|
||
|
activityResource.overrideDisplayId = overrideDisplayId;
|
||
|
activityResources.activityResources.add(activityResource);
|
||
|
if (DEBUG) {
|
||
|
Slog.d(TAG, "- creating new ref=" + resources);
|
||
|
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
|
||
|
}
|
||
|
return resources;
|
||
|
}
|
||
|
|
||
|
private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
|
||
|
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
|
||
|
cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
|
||
|
|
||
|
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
|
||
|
: new Resources(classLoader);
|
||
|
resources.setImpl(impl);
|
||
|
resources.setCallbacks(mUpdateCallbacks);
|
||
|
mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
|
||
|
if (DEBUG) {
|
||
|
Slog.d(TAG, "- creating new ref=" + resources);
|
||
|
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
|
||
|
}
|
||
|
return resources;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates base resources for a binder token. Calls to
|
||
|
*
|
||
|
* {@link #getResources(IBinder, String, String[], String[], String[], String[], Integer,
|
||
|
* Configuration, CompatibilityInfo, ClassLoader, List)} with the same binder token will have
|
||
|
* their override configurations merged with the one specified here.
|
||
|
*
|
||
|
* @param token Represents an {@link Activity} or {@link WindowContext}.
|
||
|
* @param resDir The base resource path. Can be null (only framework resources will be loaded).
|
||
|
* @param splitResDirs An array of split resource paths. Can be null.
|
||
|
* @param legacyOverlayDirs An array of overlay APK paths. Can be null.
|
||
|
* @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
|
||
|
* @param libDirs An array of resource library paths. Can be null.
|
||
|
* @param displayId The ID of the display for which to create the resources.
|
||
|
* @param overrideConfig The configuration to apply on top of the base configuration. Can be
|
||
|
* {@code null}. This provides the base override for this token.
|
||
|
* @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
|
||
|
* {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
|
||
|
* @param classLoader The class loader to use when inflating Resources. If null, the
|
||
|
* {@link ClassLoader#getSystemClassLoader()} is used.
|
||
|
* @return a Resources object from which to access resources.
|
||
|
*/
|
||
|
public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
|
||
|
@Nullable String resDir,
|
||
|
@Nullable String[] splitResDirs,
|
||
|
@Nullable String[] legacyOverlayDirs,
|
||
|
@Nullable String[] overlayPaths,
|
||
|
@Nullable String[] libDirs,
|
||
|
int displayId,
|
||
|
@Nullable Configuration overrideConfig,
|
||
|
@NonNull CompatibilityInfo compatInfo,
|
||
|
@Nullable ClassLoader classLoader,
|
||
|
@Nullable List<ResourcesLoader> loaders) {
|
||
|
try {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
|
||
|
"ResourcesManager#createBaseActivityResources");
|
||
|
final ResourcesKey key = new ResourcesKey(
|
||
|
resDir,
|
||
|
splitResDirs,
|
||
|
combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
|
||
|
libDirs,
|
||
|
displayId,
|
||
|
overrideConfig,
|
||
|
compatInfo,
|
||
|
loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
|
||
|
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
|
||
|
|
||
|
if (DEBUG) {
|
||
|
Slog.d(TAG, "createBaseActivityResources activity=" + token
|
||
|
+ " with key=" + key);
|
||
|
}
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
// Force the creation of an ActivityResourcesStruct.
|
||
|
getOrCreateActivityResourcesStructLocked(token);
|
||
|
}
|
||
|
|
||
|
// Update any existing Activity Resources references.
|
||
|
updateResourcesForActivity(token, overrideConfig, displayId);
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
Resources resources = findResourcesForActivityLocked(token, key,
|
||
|
classLoader);
|
||
|
if (resources != null) {
|
||
|
return resources;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now request an actual Resources object.
|
||
|
return createResourcesForActivity(token, key,
|
||
|
/* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
|
||
|
classLoader, /* apkSupplier */ null);
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rebases a key's override config on top of the Activity's base override.
|
||
|
*
|
||
|
* @param activityToken the token the supplied {@code key} is derived from.
|
||
|
* @param key the key to rebase
|
||
|
* @param overridesActivityDisplay whether this key is overriding the display from the token
|
||
|
*/
|
||
|
private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key,
|
||
|
boolean overridesActivityDisplay) {
|
||
|
synchronized (mLock) {
|
||
|
final ActivityResources activityResources =
|
||
|
getOrCreateActivityResourcesStructLocked(activityToken);
|
||
|
|
||
|
if (key.mDisplayId == INVALID_DISPLAY) {
|
||
|
key.mDisplayId = activityResources.overrideDisplayId;
|
||
|
}
|
||
|
|
||
|
Configuration config;
|
||
|
if (key.hasOverrideConfiguration()) {
|
||
|
config = new Configuration(activityResources.overrideConfig);
|
||
|
config.updateFrom(key.mOverrideConfiguration);
|
||
|
} else {
|
||
|
config = activityResources.overrideConfig;
|
||
|
}
|
||
|
|
||
|
if (overridesActivityDisplay
|
||
|
&& key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) {
|
||
|
if (!key.hasOverrideConfiguration()) {
|
||
|
// Make a copy to handle the case where the override config is set to defaults.
|
||
|
config = new Configuration(config);
|
||
|
}
|
||
|
|
||
|
// If this key is overriding the display from the token and the key's
|
||
|
// window config app bounds is null we need to explicitly override this to
|
||
|
// ensure the display adjustments are as expected.
|
||
|
config.windowConfiguration.setAppBounds(null);
|
||
|
}
|
||
|
|
||
|
key.mOverrideConfiguration.setTo(config);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rebases a key's override config with display metrics of the {@code overrideDisplay} paired
|
||
|
* with the {code displayAdjustments}.
|
||
|
*
|
||
|
* @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
|
||
|
*/
|
||
|
private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) {
|
||
|
final Configuration temp = new Configuration();
|
||
|
|
||
|
DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
|
||
|
daj.setCompatibilityInfo(key.mCompatInfo);
|
||
|
|
||
|
final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj);
|
||
|
applyDisplayMetricsToConfiguration(dm, temp);
|
||
|
|
||
|
if (key.hasOverrideConfiguration()) {
|
||
|
temp.updateFrom(key.mOverrideConfiguration);
|
||
|
}
|
||
|
key.mOverrideConfiguration.setTo(temp);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check WeakReferences and remove any dead references so they don't pile up.
|
||
|
*/
|
||
|
private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
|
||
|
ReferenceQueue<T> referenceQueue) {
|
||
|
cleanupReferences(references, referenceQueue, Function.identity());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check WeakReferences and remove any dead references so they don't pile up.
|
||
|
*/
|
||
|
private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers,
|
||
|
ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) {
|
||
|
Reference<? extends T> enqueuedRef = referenceQueue.poll();
|
||
|
if (enqueuedRef == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
|
||
|
for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) {
|
||
|
deadReferences.add(enqueuedRef);
|
||
|
}
|
||
|
|
||
|
ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> {
|
||
|
WeakReference<T> ref = unwrappingFunction.apply(refContainer);
|
||
|
return ref == null || deadReferences.contains(ref);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
|
||
|
* into the supplier. This should be done while the lock is not held to prevent performing I/O
|
||
|
* while holding the lock.
|
||
|
*/
|
||
|
private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
|
||
|
"ResourcesManager#createApkAssetsSupplierNotLocked");
|
||
|
try {
|
||
|
if (DEBUG && Thread.holdsLock(mLock)) {
|
||
|
Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
|
||
|
+ " is holding mLock", new Throwable());
|
||
|
}
|
||
|
|
||
|
final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
|
||
|
final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
|
||
|
for (int i = 0, n = apkKeys.size(); i < n; i++) {
|
||
|
final ApkKey apkKey = apkKeys.get(i);
|
||
|
try {
|
||
|
supplier.load(apkKey);
|
||
|
} catch (IOException e) {
|
||
|
Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
|
||
|
}
|
||
|
}
|
||
|
return supplier;
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a Resources object set with a ResourcesImpl object matching the given key.
|
||
|
*
|
||
|
* @param key The key describing the parameters of the ResourcesImpl object.
|
||
|
* @param classLoader The classloader to use for the Resources object.
|
||
|
* If null, {@link ClassLoader#getSystemClassLoader()} is used.
|
||
|
* @return A Resources object that gets updated when
|
||
|
* {@link #applyConfigurationToResources(Configuration, CompatibilityInfo)}
|
||
|
* is called.
|
||
|
*/
|
||
|
@Nullable
|
||
|
private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
|
||
|
@Nullable ApkAssetsSupplier apkSupplier) {
|
||
|
synchronized (mLock) {
|
||
|
if (DEBUG) {
|
||
|
Throwable here = new Throwable();
|
||
|
here.fillInStackTrace();
|
||
|
Slog.w(TAG, "!! Create resources for key=" + key, here);
|
||
|
}
|
||
|
|
||
|
ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
|
||
|
if (resourcesImpl == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Nullable
|
||
|
private Resources createResourcesForActivity(@NonNull IBinder activityToken,
|
||
|
@NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
|
||
|
@Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
|
||
|
@Nullable ApkAssetsSupplier apkSupplier) {
|
||
|
synchronized (mLock) {
|
||
|
if (DEBUG) {
|
||
|
Throwable here = new Throwable();
|
||
|
here.fillInStackTrace();
|
||
|
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
|
||
|
}
|
||
|
|
||
|
ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
|
||
|
if (resourcesImpl == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
|
||
|
overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets or creates a new Resources object associated with the IBinder token. References returned
|
||
|
* by this method live as long as the Activity, meaning they can be cached and used by the
|
||
|
* Activity even after a configuration change. If any other parameter is changed
|
||
|
* (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
|
||
|
* is updated and handed back to the caller. However, changing the class loader will result in a
|
||
|
* new Resources object.
|
||
|
* <p/>
|
||
|
* If activityToken is null, a cached Resources object will be returned if it matches the
|
||
|
* input parameters. Otherwise a new Resources object that satisfies these parameters is
|
||
|
* returned.
|
||
|
*
|
||
|
* @param activityToken Represents an Activity. If null, global resources are assumed.
|
||
|
* @param resDir The base resource path. Can be null (only framework resources will be loaded).
|
||
|
* @param splitResDirs An array of split resource paths. Can be null.
|
||
|
* @param legacyOverlayDirs An array of overlay APK paths. Can be null.
|
||
|
* @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
|
||
|
* @param libDirs An array of resource library paths. Can be null.
|
||
|
* @param overrideDisplayId The ID of the display for which the returned Resources should be
|
||
|
* based. This will cause display-based configuration properties to override those of the base
|
||
|
* Resources for the {@code activityToken}, or the global configuration if {@code activityToken}
|
||
|
* is null.
|
||
|
* @param overrideConfig The configuration to apply on top of the base configuration. Can be
|
||
|
* null. Mostly used with Activities that are in multi-window which may override width and
|
||
|
* height properties from the base config.
|
||
|
* @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
|
||
|
* {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
|
||
|
* @param classLoader The class loader to use when inflating Resources. If null, the
|
||
|
* {@link ClassLoader#getSystemClassLoader()} is used.
|
||
|
* @return a Resources object from which to access resources.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public Resources getResources(
|
||
|
@Nullable IBinder activityToken,
|
||
|
@Nullable String resDir,
|
||
|
@Nullable String[] splitResDirs,
|
||
|
@Nullable String[] legacyOverlayDirs,
|
||
|
@Nullable String[] overlayPaths,
|
||
|
@Nullable String[] libDirs,
|
||
|
@Nullable Integer overrideDisplayId,
|
||
|
@Nullable Configuration overrideConfig,
|
||
|
@NonNull CompatibilityInfo compatInfo,
|
||
|
@Nullable ClassLoader classLoader,
|
||
|
@Nullable List<ResourcesLoader> loaders) {
|
||
|
try {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
|
||
|
final ResourcesKey key = new ResourcesKey(
|
||
|
resDir,
|
||
|
splitResDirs,
|
||
|
combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
|
||
|
libDirs,
|
||
|
overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
|
||
|
overrideConfig,
|
||
|
compatInfo,
|
||
|
loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
|
||
|
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
|
||
|
|
||
|
// Preload the ApkAssets required by the key to prevent performing heavy I/O while the
|
||
|
// ResourcesManager lock is held.
|
||
|
final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
|
||
|
|
||
|
if (overrideDisplayId != null) {
|
||
|
rebaseKeyForDisplay(key, overrideDisplayId);
|
||
|
}
|
||
|
|
||
|
Resources resources;
|
||
|
if (activityToken != null) {
|
||
|
Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
|
||
|
rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
|
||
|
resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
|
||
|
overrideDisplayId, classLoader, assetsSupplier);
|
||
|
} else {
|
||
|
resources = createResources(key, classLoader, assetsSupplier);
|
||
|
}
|
||
|
return resources;
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates an Activity's Resources object with overrideConfig. The Resources object
|
||
|
* that was previously returned by {@link #getResources(IBinder, String, String[], String[],
|
||
|
* String[], String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still
|
||
|
* valid and will have the updated configuration.
|
||
|
*
|
||
|
* @param activityToken The Activity token.
|
||
|
* @param overrideConfig The configuration override to update.
|
||
|
* @param displayId Id of the display where activity currently resides.
|
||
|
*/
|
||
|
public void updateResourcesForActivity(@NonNull IBinder activityToken,
|
||
|
@Nullable Configuration overrideConfig, int displayId) {
|
||
|
try {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
|
||
|
"ResourcesManager#updateResourcesForActivity");
|
||
|
if (displayId == INVALID_DISPLAY) {
|
||
|
throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY");
|
||
|
}
|
||
|
synchronized (mLock) {
|
||
|
final ActivityResources activityResources =
|
||
|
getOrCreateActivityResourcesStructLocked(activityToken);
|
||
|
|
||
|
boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
|
||
|
if (Objects.equals(activityResources.overrideConfig, overrideConfig)
|
||
|
&& !movedToDifferentDisplay) {
|
||
|
// They are the same and no change of display id, no work to do.
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Grab a copy of the old configuration so we can create the delta's of each
|
||
|
// Resources object associated with this Activity.
|
||
|
final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
|
||
|
|
||
|
// Update the Activity's base override.
|
||
|
if (overrideConfig != null) {
|
||
|
activityResources.overrideConfig.setTo(overrideConfig);
|
||
|
} else {
|
||
|
activityResources.overrideConfig.unset();
|
||
|
}
|
||
|
|
||
|
// Update the Activity's override display id.
|
||
|
activityResources.overrideDisplayId = displayId;
|
||
|
|
||
|
// If a application info update was scheduled to occur in this process but has not
|
||
|
// occurred yet, apply it now so the resources objects will have updated paths if
|
||
|
// the assets sequence changed.
|
||
|
applyAllPendingAppInfoUpdates();
|
||
|
|
||
|
if (DEBUG) {
|
||
|
Throwable here = new Throwable();
|
||
|
here.fillInStackTrace();
|
||
|
Slog.d(TAG, "updating resources override for activity=" + activityToken
|
||
|
+ " from oldConfig="
|
||
|
+ Configuration.resourceQualifierString(oldConfig)
|
||
|
+ " to newConfig="
|
||
|
+ Configuration.resourceQualifierString(
|
||
|
activityResources.overrideConfig) + " displayId=" + displayId,
|
||
|
here);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Rebase each Resources associated with this Activity.
|
||
|
final int refCount = activityResources.activityResources.size();
|
||
|
for (int i = 0; i < refCount; i++) {
|
||
|
final ActivityResource activityResource =
|
||
|
activityResources.activityResources.get(i);
|
||
|
|
||
|
final Resources resources = activityResource.resources.get();
|
||
|
if (resources == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
|
||
|
overrideConfig, displayId);
|
||
|
if (newKey == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
|
||
|
// constructions.
|
||
|
final ResourcesImpl resourcesImpl =
|
||
|
findOrCreateResourcesImplForKeyLocked(newKey);
|
||
|
if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
|
||
|
// Set the ResourcesImpl, updating it for all users of this Resources
|
||
|
// object.
|
||
|
resources.setImpl(resourcesImpl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rebases an updated override config over any old override config and returns the new one
|
||
|
* that an Activity's Resources should be set to.
|
||
|
*/
|
||
|
@Nullable
|
||
|
private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource,
|
||
|
@Nullable Configuration newOverrideConfig, int displayId) {
|
||
|
final Resources resources = activityResource.resources.get();
|
||
|
if (resources == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Extract the ResourcesKey that was last used to create the Resources for this
|
||
|
// activity.
|
||
|
final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
|
||
|
if (oldKey == null) {
|
||
|
Slog.e(TAG, "can't find ResourcesKey for resources impl="
|
||
|
+ resources.getImpl());
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Build the new override configuration for this ResourcesKey.
|
||
|
final Configuration rebasedOverrideConfig = new Configuration();
|
||
|
if (newOverrideConfig != null) {
|
||
|
rebasedOverrideConfig.setTo(newOverrideConfig);
|
||
|
}
|
||
|
|
||
|
final Integer overrideDisplayId = activityResource.overrideDisplayId;
|
||
|
if (overrideDisplayId != null) {
|
||
|
DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig);
|
||
|
displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig);
|
||
|
displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo);
|
||
|
|
||
|
DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments);
|
||
|
applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig);
|
||
|
}
|
||
|
|
||
|
final boolean hasOverrideConfig =
|
||
|
!activityResource.overrideConfig.equals(Configuration.EMPTY);
|
||
|
if (hasOverrideConfig) {
|
||
|
rebasedOverrideConfig.updateFrom(activityResource.overrideConfig);
|
||
|
}
|
||
|
|
||
|
if (activityResource.overrideDisplayId != null
|
||
|
&& activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) {
|
||
|
// If this activity resource is overriding the display from the token and the key's
|
||
|
// window config app bounds is null we need to explicitly override this to
|
||
|
// ensure the display adjustments are as expected.
|
||
|
rebasedOverrideConfig.windowConfiguration.setAppBounds(null);
|
||
|
}
|
||
|
|
||
|
// Ensure the new key keeps the expected override display instead of the new token display.
|
||
|
displayId = overrideDisplayId != null ? overrideDisplayId : displayId;
|
||
|
|
||
|
// Create the new ResourcesKey with the rebased override config.
|
||
|
final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
|
||
|
oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs,
|
||
|
displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
|
||
|
|
||
|
if (DEBUG) {
|
||
|
Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
|
||
|
+ " to newKey=" + newKey + ", displayId=" + displayId);
|
||
|
}
|
||
|
|
||
|
return newKey;
|
||
|
}
|
||
|
|
||
|
public void appendPendingAppInfoUpdate(@NonNull String[] oldSourceDirs,
|
||
|
@NonNull ApplicationInfo appInfo) {
|
||
|
synchronized (mLock) {
|
||
|
if (mPendingAppInfoUpdates == null) {
|
||
|
mPendingAppInfoUpdates = new ArrayList<>();
|
||
|
}
|
||
|
// Clear previous app info changes for a package to prevent multiple ResourcesImpl
|
||
|
// recreations when the recreation caused by this update completely overrides the
|
||
|
// previous pending changes.
|
||
|
for (int i = mPendingAppInfoUpdates.size() - 1; i >= 0; i--) {
|
||
|
if (ArrayUtils.containsAll(oldSourceDirs, mPendingAppInfoUpdates.get(i).first)) {
|
||
|
mPendingAppInfoUpdates.remove(i);
|
||
|
}
|
||
|
}
|
||
|
mPendingAppInfoUpdates.add(new Pair<>(oldSourceDirs, appInfo));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public final void applyAllPendingAppInfoUpdates() {
|
||
|
synchronized (mLock) {
|
||
|
if (mPendingAppInfoUpdates != null) {
|
||
|
for (int i = 0, n = mPendingAppInfoUpdates.size(); i < n; i++) {
|
||
|
final Pair<String[], ApplicationInfo> appInfo = mPendingAppInfoUpdates.get(i);
|
||
|
applyNewResourceDirsLocked(appInfo.first, appInfo.second);
|
||
|
}
|
||
|
mPendingAppInfoUpdates = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public final boolean applyConfigurationToResources(@NonNull Configuration config,
|
||
|
@Nullable CompatibilityInfo compat) {
|
||
|
synchronized (mLock) {
|
||
|
try {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
|
||
|
"ResourcesManager#applyConfigurationToResources");
|
||
|
|
||
|
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
|
||
|
if (DEBUG || DEBUG_CONFIGURATION) {
|
||
|
Slog.v(TAG, "Skipping new config: curSeq="
|
||
|
+ mResConfiguration.seq + ", newSeq=" + config.seq);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int changes = mResConfiguration.updateFrom(config);
|
||
|
if (compat != null && (mResCompatibilityInfo == null
|
||
|
|| !mResCompatibilityInfo.equals(compat))) {
|
||
|
mResCompatibilityInfo = compat;
|
||
|
changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
|
||
|
| ActivityInfo.CONFIG_SCREEN_SIZE
|
||
|
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
|
||
|
}
|
||
|
|
||
|
// If a application info update was scheduled to occur in this process but has not
|
||
|
// occurred yet, apply it now so the resources objects will have updated paths when
|
||
|
// the assets sequence changes.
|
||
|
if ((changes & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
|
||
|
applyAllPendingAppInfoUpdates();
|
||
|
}
|
||
|
|
||
|
final DisplayMetrics displayMetrics = getDisplayMetrics(config);
|
||
|
Resources.updateSystemConfiguration(config, displayMetrics, compat);
|
||
|
|
||
|
ApplicationPackageManager.configurationChanged();
|
||
|
|
||
|
Configuration tmpConfig = new Configuration();
|
||
|
|
||
|
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
|
||
|
ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
|
||
|
ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
|
||
|
if (r != null) {
|
||
|
applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
|
||
|
} else {
|
||
|
mResourceImpls.removeAt(i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return changes != 0;
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
|
||
|
@Nullable CompatibilityInfo compat, Configuration tmpConfig,
|
||
|
ResourcesKey key, ResourcesImpl resourcesImpl) {
|
||
|
if (DEBUG || DEBUG_CONFIGURATION) {
|
||
|
Slog.v(TAG, "Changing resources "
|
||
|
+ resourcesImpl + " config to: " + config);
|
||
|
}
|
||
|
|
||
|
tmpConfig.setTo(config);
|
||
|
if (key.hasOverrideConfiguration()) {
|
||
|
tmpConfig.updateFrom(key.mOverrideConfiguration);
|
||
|
}
|
||
|
|
||
|
// Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
|
||
|
// a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
|
||
|
// update internally.
|
||
|
DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
|
||
|
if (compat != null) {
|
||
|
daj = new DisplayAdjustments(daj);
|
||
|
daj.setCompatibilityInfo(compat);
|
||
|
}
|
||
|
daj.setConfiguration(tmpConfig);
|
||
|
DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj);
|
||
|
|
||
|
resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Appends the library asset path to any ResourcesImpl object that contains the main
|
||
|
* assetPath.
|
||
|
* @param assetPath The main asset path for which to add the library asset path.
|
||
|
* @param libAsset The library asset path to add.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
|
||
|
appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset });
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Appends the library asset paths to any ResourcesImpl object that contains the main
|
||
|
* assetPath.
|
||
|
* @param assetPath The main asset path for which to add the library asset path.
|
||
|
* @param libAssets The library asset paths to add.
|
||
|
*/
|
||
|
public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
|
||
|
synchronized (mLock) {
|
||
|
// Record which ResourcesImpl need updating
|
||
|
// (and what ResourcesKey they should update to).
|
||
|
final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
|
||
|
|
||
|
final int implCount = mResourceImpls.size();
|
||
|
for (int i = 0; i < implCount; i++) {
|
||
|
final ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
|
||
|
final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
|
||
|
if (impl != null && Objects.equals(key.mResDir, assetPath)) {
|
||
|
String[] newLibAssets = key.mLibDirs;
|
||
|
for (String libAsset : libAssets) {
|
||
|
newLibAssets =
|
||
|
ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
|
||
|
}
|
||
|
|
||
|
if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
|
||
|
updatedResourceKeys.put(impl, new ResourcesKey(
|
||
|
key.mResDir,
|
||
|
key.mSplitResDirs,
|
||
|
key.mOverlayPaths,
|
||
|
newLibAssets,
|
||
|
key.mDisplayId,
|
||
|
key.mOverrideConfiguration,
|
||
|
key.mCompatInfo,
|
||
|
key.mLoaders));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
redirectResourcesToNewImplLocked(updatedResourceKeys);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void appendLibAssetsLocked(String[] libAssets) {
|
||
|
synchronized (mLock) {
|
||
|
// Record which ResourcesImpl need updating
|
||
|
// (and what ResourcesKey they should update to).
|
||
|
final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
|
||
|
|
||
|
final int implCount = mResourceImpls.size();
|
||
|
for (int i = 0; i < implCount; i++) {
|
||
|
final ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
|
||
|
final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
|
||
|
if (impl == null) {
|
||
|
Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
|
||
|
+ "append shared library assets for next ResourcesImpl.");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var newDirs = new ArrayList<String>();
|
||
|
var dirsSet = new ArraySet<String>();
|
||
|
if (key.mLibDirs != null) {
|
||
|
final int dirsLength = key.mLibDirs.length;
|
||
|
for (int k = 0; k < dirsLength; k++) {
|
||
|
newDirs.add(key.mLibDirs[k]);
|
||
|
dirsSet.add(key.mLibDirs[k]);
|
||
|
}
|
||
|
}
|
||
|
final int assetsLength = libAssets.length;
|
||
|
for (int j = 0; j < assetsLength; j++) {
|
||
|
if (dirsSet.add(libAssets[j])) {
|
||
|
newDirs.add(libAssets[j]);
|
||
|
}
|
||
|
}
|
||
|
String[] newLibAssets = newDirs.toArray(new String[0]);
|
||
|
if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
|
||
|
updatedResourceKeys.put(impl, new ResourcesKey(
|
||
|
key.mResDir,
|
||
|
key.mSplitResDirs,
|
||
|
key.mOverlayPaths,
|
||
|
newLibAssets,
|
||
|
key.mDisplayId,
|
||
|
key.mOverrideConfiguration,
|
||
|
key.mCompatInfo,
|
||
|
key.mLoaders));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
redirectAllResourcesToNewImplLocked(updatedResourceKeys);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
|
||
|
@NonNull final ApplicationInfo appInfo) {
|
||
|
try {
|
||
|
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
|
||
|
"ResourcesManager#applyNewResourceDirsLocked");
|
||
|
|
||
|
String baseCodePath = appInfo.getBaseCodePath();
|
||
|
|
||
|
final int myUid = Process.myUid();
|
||
|
String[] newSplitDirs = appInfo.uid == myUid
|
||
|
? appInfo.splitSourceDirs
|
||
|
: appInfo.splitPublicSourceDirs;
|
||
|
|
||
|
// ApplicationInfo is mutable, so clone the arrays to prevent outside modification
|
||
|
String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
|
||
|
String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs,
|
||
|
appInfo.overlayPaths);
|
||
|
|
||
|
if (appInfo.uid == myUid) {
|
||
|
addApplicationPathsLocked(baseCodePath, copiedSplitDirs);
|
||
|
}
|
||
|
|
||
|
final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
|
||
|
final int implCount = mResourceImpls.size();
|
||
|
for (int i = 0; i < implCount; i++) {
|
||
|
final ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
|
||
|
final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
|
||
|
|
||
|
if (impl == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (key.mResDir == null
|
||
|
|| key.mResDir.equals(baseCodePath)
|
||
|
|| ArrayUtils.contains(oldSourceDirs, key.mResDir)) {
|
||
|
updatedResourceKeys.put(impl, new ResourcesKey(
|
||
|
baseCodePath,
|
||
|
copiedSplitDirs,
|
||
|
copiedResourceDirs,
|
||
|
key.mLibDirs,
|
||
|
key.mDisplayId,
|
||
|
key.mOverrideConfiguration,
|
||
|
key.mCompatInfo,
|
||
|
key.mLoaders
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
redirectResourcesToNewImplLocked(updatedResourceKeys);
|
||
|
} finally {
|
||
|
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates an array with the contents of {@param overlayPaths} and the unique elements of
|
||
|
* {@param resourceDirs}.
|
||
|
*
|
||
|
* {@link ApplicationInfo#resourceDirs} only contains paths of overlays APKs.
|
||
|
* {@link ApplicationInfo#overlayPaths} was created to contain paths of overlay of varying file
|
||
|
* formats. It also contains the contents of {@code resourceDirs} because the order of loaded
|
||
|
* overlays matter. In case {@code resourceDirs} contains overlay APK paths that are not present
|
||
|
* in overlayPaths (perhaps an app inserted an additional overlay path into a
|
||
|
* {@code resourceDirs}), this method is used to combine the contents of {@code resourceDirs}
|
||
|
* that do not exist in {@code overlayPaths}} and {@code overlayPaths}}.
|
||
|
*/
|
||
|
@Nullable
|
||
|
private static String[] combinedOverlayPaths(@Nullable String[] resourceDirs,
|
||
|
@Nullable String[] overlayPaths) {
|
||
|
if (resourceDirs == null) {
|
||
|
return ArrayUtils.cloneOrNull(overlayPaths);
|
||
|
} else if(overlayPaths == null) {
|
||
|
return ArrayUtils.cloneOrNull(resourceDirs);
|
||
|
} else {
|
||
|
final var paths = new ArrayList<String>(overlayPaths.length + resourceDirs.length);
|
||
|
for (final String path : overlayPaths) {
|
||
|
paths.add(path);
|
||
|
}
|
||
|
for (final String path : resourceDirs) {
|
||
|
if (!paths.contains(path)) {
|
||
|
paths.add(path);
|
||
|
}
|
||
|
}
|
||
|
return paths.toArray(new String[0]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void redirectResourcesToNewImplLocked(
|
||
|
@NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
|
||
|
// Bail early if there is no work to do.
|
||
|
if (updatedResourceKeys.isEmpty()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Update any references to ResourcesImpl that require reloading.
|
||
|
final int resourcesCount = mResourceReferences.size();
|
||
|
for (int i = 0; i < resourcesCount; i++) {
|
||
|
final WeakReference<Resources> ref = mResourceReferences.get(i);
|
||
|
final Resources r = ref != null ? ref.get() : null;
|
||
|
if (r != null) {
|
||
|
final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
|
||
|
if (key != null) {
|
||
|
final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
|
||
|
if (impl == null) {
|
||
|
throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
|
||
|
}
|
||
|
r.setImpl(impl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update any references to ResourcesImpl that require reloading for each Activity.
|
||
|
for (ActivityResources activityResources : mActivityResourceReferences.values()) {
|
||
|
final int resCount = activityResources.activityResources.size();
|
||
|
for (int i = 0; i < resCount; i++) {
|
||
|
final ActivityResource activityResource =
|
||
|
activityResources.activityResources.get(i);
|
||
|
final Resources r = activityResource != null
|
||
|
? activityResource.resources.get() : null;
|
||
|
if (r != null) {
|
||
|
final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
|
||
|
if (key != null) {
|
||
|
final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
|
||
|
if (impl == null) {
|
||
|
throw new Resources.NotFoundException(
|
||
|
"failed to redirect ResourcesImpl");
|
||
|
}
|
||
|
r.setImpl(impl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Another redirect function which will loop through all Resources and reload ResourcesImpl
|
||
|
// if it needs a shared library asset paths update.
|
||
|
private void redirectAllResourcesToNewImplLocked(
|
||
|
@NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
|
||
|
cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue);
|
||
|
|
||
|
// Update any references to ResourcesImpl that require reloading.
|
||
|
final int resourcesCount = mAllResourceReferences.size();
|
||
|
for (int i = 0; i < resourcesCount; i++) {
|
||
|
final WeakReference<Resources> ref = mAllResourceReferences.get(i);
|
||
|
final Resources r = ref != null ? ref.get() : null;
|
||
|
if (r != null) {
|
||
|
final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
|
||
|
if (key != null) {
|
||
|
final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
|
||
|
if (impl == null) {
|
||
|
throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
|
||
|
}
|
||
|
r.setImpl(impl);
|
||
|
} else {
|
||
|
// ResourcesKey is null which means the ResourcesImpl could belong to a
|
||
|
// Resources created by application through Resources constructor and was not
|
||
|
// managed by ResourcesManager, so the ResourcesImpl needs to be recreated to
|
||
|
// have shared library asset paths appended if there are any.
|
||
|
if (r.getImpl() != null) {
|
||
|
final ResourcesImpl oldImpl = r.getImpl();
|
||
|
// ResourcesImpl constructor will help to append shared library asset paths.
|
||
|
final ResourcesImpl newImpl = new ResourcesImpl(oldImpl.getAssets(),
|
||
|
oldImpl.getMetrics(), oldImpl.getConfiguration(),
|
||
|
oldImpl.getDisplayAdjustments());
|
||
|
r.setImpl(newImpl);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the LocaleConfig current set
|
||
|
*/
|
||
|
public LocaleConfig getLocaleConfig() {
|
||
|
return mLocaleConfig;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the LocaleConfig of the app
|
||
|
*/
|
||
|
public void setLocaleConfig(LocaleConfig localeConfig) {
|
||
|
if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
|
||
|
&& !localeConfig.getSupportedLocales().isEmpty()) {
|
||
|
mLocaleConfig = localeConfig;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class UpdateHandler implements Resources.UpdateCallbacks {
|
||
|
|
||
|
/**
|
||
|
* Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
|
||
|
* instance uses.
|
||
|
*/
|
||
|
@Override
|
||
|
public void onLoadersChanged(@NonNull Resources resources,
|
||
|
@NonNull List<ResourcesLoader> newLoader) {
|
||
|
synchronized (mLock) {
|
||
|
final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
|
||
|
if (oldKey == null) {
|
||
|
throw new IllegalArgumentException("Cannot modify resource loaders of"
|
||
|
+ " ResourcesImpl not registered with ResourcesManager");
|
||
|
}
|
||
|
|
||
|
final ResourcesKey newKey = new ResourcesKey(
|
||
|
oldKey.mResDir,
|
||
|
oldKey.mSplitResDirs,
|
||
|
oldKey.mOverlayPaths,
|
||
|
oldKey.mLibDirs,
|
||
|
oldKey.mDisplayId,
|
||
|
oldKey.mOverrideConfiguration,
|
||
|
oldKey.mCompatInfo,
|
||
|
newLoader.toArray(new ResourcesLoader[0]));
|
||
|
|
||
|
final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
|
||
|
resources.setImpl(impl);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
|
||
|
* {@code loader} to apply any changes of the set of {@link ApkAssets}.
|
||
|
**/
|
||
|
@Override
|
||
|
public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
|
||
|
synchronized (mLock) {
|
||
|
final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
|
||
|
new ArrayMap<>();
|
||
|
|
||
|
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
|
||
|
final ResourcesKey key = mResourceImpls.keyAt(i);
|
||
|
final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
|
||
|
if (impl == null || impl.refersTo(null)
|
||
|
|| !ArrayUtils.contains(key.mLoaders, loader)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
mResourceImpls.remove(key);
|
||
|
updatedResourceImplKeys.put(impl.get(), key);
|
||
|
}
|
||
|
|
||
|
redirectResourcesToNewImplLocked(updatedResourceImplKeys);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static class SharedLibraryAssets{
|
||
|
private final String[] mAssetPaths;
|
||
|
|
||
|
SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
|
||
|
String[] resourceDirs, String[] overlayPaths) {
|
||
|
mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
|
||
|
resourceDirs, overlayPaths);
|
||
|
}
|
||
|
|
||
|
private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
|
||
|
String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
|
||
|
final String[][] inputLists = {
|
||
|
splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
|
||
|
};
|
||
|
|
||
|
final ArraySet<String> assetPathSet = new ArraySet<>();
|
||
|
final List<String> assetPathList = new ArrayList<>();
|
||
|
if (sourceDir != null) {
|
||
|
assetPathSet.add(sourceDir);
|
||
|
assetPathList.add(sourceDir);
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < inputLists.length; i++) {
|
||
|
if (inputLists[i] != null) {
|
||
|
for (int j = 0; j < inputLists[i].length; j++) {
|
||
|
if (assetPathSet.add(inputLists[i][j])) {
|
||
|
assetPathList.add(inputLists[i][j]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return assetPathList.toArray(new String[0]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return all the asset paths of this collected in this class.
|
||
|
*/
|
||
|
public @NonNull String[] getAllAssetPaths() {
|
||
|
return mAssetPaths;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
|
||
|
return new ArrayMap<>(mSharedLibAssetsMap);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add all resources references to the list which is designed to help to append shared library
|
||
|
* asset paths. This is invoked in Resources constructor to include all Resources instances.
|
||
|
*/
|
||
|
public void registerAllResourcesReference(@NonNull Resources resources) {
|
||
|
if (android.content.res.Flags.registerResourcePaths()) {
|
||
|
synchronized (mLock) {
|
||
|
mAllResourceReferences.add(
|
||
|
new WeakReference<>(resources, mAllResourceReferencesQueue));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|