Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.app;
     18 
     19 import static android.app.ActivityThread.DEBUG_CONFIGURATION;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.pm.ActivityInfo;
     24 import android.content.res.AssetManager;
     25 import android.content.res.CompatResources;
     26 import android.content.res.CompatibilityInfo;
     27 import android.content.res.Configuration;
     28 import android.content.res.Resources;
     29 import android.content.res.ResourcesImpl;
     30 import android.content.res.ResourcesKey;
     31 import android.hardware.display.DisplayManagerGlobal;
     32 import android.os.IBinder;
     33 import android.os.Trace;
     34 import android.util.ArrayMap;
     35 import android.util.DisplayMetrics;
     36 import android.util.Log;
     37 import android.util.Pair;
     38 import android.util.Slog;
     39 import android.view.Display;
     40 import android.view.DisplayAdjustments;
     41 
     42 import com.android.internal.annotations.VisibleForTesting;
     43 import com.android.internal.util.ArrayUtils;
     44 
     45 import java.lang.ref.WeakReference;
     46 import java.util.ArrayList;
     47 import java.util.Iterator;
     48 import java.util.Map;
     49 import java.util.Objects;
     50 import java.util.WeakHashMap;
     51 import java.util.function.Predicate;
     52 
     53 /** @hide */
     54 public class ResourcesManager {
     55     static final String TAG = "ResourcesManager";
     56     private static final boolean DEBUG = false;
     57 
     58     private static ResourcesManager sResourcesManager;
     59 
     60     /**
     61      * Predicate that returns true if a WeakReference is gc'ed.
     62      */
     63     private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
     64             new Predicate<WeakReference<Resources>>() {
     65                 @Override
     66                 public boolean test(WeakReference<Resources> weakRef) {
     67                     return weakRef == null || weakRef.get() == null;
     68                 }
     69             };
     70 
     71     /**
     72      * The global compatibility settings.
     73      */
     74     private CompatibilityInfo mResCompatibilityInfo;
     75 
     76     /**
     77      * The global configuration upon which all Resources are based. Multi-window Resources
     78      * apply their overrides to this configuration.
     79      */
     80     private final Configuration mResConfiguration = new Configuration();
     81 
     82     /**
     83      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
     84      * which should be reused as much as possible.
     85      */
     86     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
     87             new ArrayMap<>();
     88 
     89     /**
     90      * A list of Resource references that can be reused.
     91      */
     92     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
     93 
     94     /**
     95      * Resources and base configuration override associated with an Activity.
     96      */
     97     private static class ActivityResources {
     98         public final Configuration overrideConfig = new Configuration();
     99         public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
    100     }
    101 
    102     /**
    103      * Each Activity may has a base override configuration that is applied to each Resources object,
    104      * which in turn may have their own override configuration specified.
    105      */
    106     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
    107             new WeakHashMap<>();
    108 
    109     /**
    110      * A cache of DisplayId, DisplayAdjustments to Display.
    111      */
    112     private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
    113             mAdjustedDisplays = new ArrayMap<>();
    114 
    115     public static ResourcesManager getInstance() {
    116         synchronized (ResourcesManager.class) {
    117             if (sResourcesManager == null) {
    118                 sResourcesManager = new ResourcesManager();
    119             }
    120             return sResourcesManager;
    121         }
    122     }
    123 
    124     /**
    125      * Invalidate and destroy any resources that reference content under the
    126      * given filesystem path. Typically used when unmounting a storage device to
    127      * try as hard as possible to release any open FDs.
    128      */
    129     public void invalidatePath(String path) {
    130         synchronized (this) {
    131             int count = 0;
    132             for (int i = 0; i < mResourceImpls.size();) {
    133                 final ResourcesKey key = mResourceImpls.keyAt(i);
    134                 if (key.isPathReferenced(path)) {
    135                     cleanupResourceImpl(key);
    136                     count++;
    137                 } else {
    138                     i++;
    139                 }
    140             }
    141             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
    142         }
    143     }
    144 
    145     public Configuration getConfiguration() {
    146         synchronized (this) {
    147             return mResConfiguration;
    148         }
    149     }
    150 
    151     DisplayMetrics getDisplayMetrics() {
    152         return getDisplayMetrics(Display.DEFAULT_DISPLAY,
    153                 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
    154     }
    155 
    156     /**
    157      * Protected so that tests can override and returns something a fixed value.
    158      */
    159     @VisibleForTesting
    160     protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
    161         DisplayMetrics dm = new DisplayMetrics();
    162         final Display display = getAdjustedDisplay(displayId, da);
    163         if (display != null) {
    164             display.getMetrics(dm);
    165         } else {
    166             dm.setToDefaults();
    167         }
    168         return dm;
    169     }
    170 
    171     private static void applyNonDefaultDisplayMetricsToConfiguration(
    172             @NonNull DisplayMetrics dm, @NonNull Configuration config) {
    173         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
    174         config.densityDpi = dm.densityDpi;
    175         config.screenWidthDp = (int) (dm.widthPixels / dm.density);
    176         config.screenHeightDp = (int) (dm.heightPixels / dm.density);
    177         int sl = Configuration.resetScreenLayout(config.screenLayout);
    178         if (dm.widthPixels > dm.heightPixels) {
    179             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
    180             config.screenLayout = Configuration.reduceScreenLayout(sl,
    181                     config.screenWidthDp, config.screenHeightDp);
    182         } else {
    183             config.orientation = Configuration.ORIENTATION_PORTRAIT;
    184             config.screenLayout = Configuration.reduceScreenLayout(sl,
    185                     config.screenHeightDp, config.screenWidthDp);
    186         }
    187         config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate
    188         config.compatScreenWidthDp = config.screenWidthDp;
    189         config.compatScreenHeightDp = config.screenHeightDp;
    190         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
    191     }
    192 
    193     public boolean applyCompatConfigurationLocked(int displayDensity,
    194             @NonNull Configuration compatConfiguration) {
    195         if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
    196             mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
    197             return true;
    198         }
    199         return false;
    200     }
    201 
    202     /**
    203      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
    204      * available. This method is only used within {@link ResourcesManager} to calculate display
    205      * metrics based on a set {@link DisplayAdjustments}. All other usages should instead call
    206      * {@link ResourcesManager#getAdjustedDisplay(int, Resources)}.
    207      *
    208      * @param displayId display Id.
    209      * @param displayAdjustments display adjustments.
    210      */
    211     private Display getAdjustedDisplay(final int displayId,
    212             @Nullable DisplayAdjustments displayAdjustments) {
    213         final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
    214                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
    215         final Pair<Integer, DisplayAdjustments> key =
    216                 Pair.create(displayId, displayAdjustmentsCopy);
    217         synchronized (this) {
    218             WeakReference<Display> wd = mAdjustedDisplays.get(key);
    219             if (wd != null) {
    220                 final Display display = wd.get();
    221                 if (display != null) {
    222                     return display;
    223                 }
    224             }
    225             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    226             if (dm == null) {
    227                 // may be null early in system startup
    228                 return null;
    229             }
    230             final Display display = dm.getCompatibleDisplay(displayId, key.second);
    231             if (display != null) {
    232                 mAdjustedDisplays.put(key, new WeakReference<>(display));
    233             }
    234             return display;
    235         }
    236     }
    237 
    238     /**
    239      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
    240      * available.
    241      *
    242      * @param displayId display Id.
    243      * @param resources The {@link Resources} backing the display adjustments.
    244      */
    245     public Display getAdjustedDisplay(final int displayId, Resources resources) {
    246         synchronized (this) {
    247             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    248             if (dm == null) {
    249                 // may be null early in system startup
    250                 return null;
    251             }
    252             return dm.getCompatibleDisplay(displayId, resources);
    253         }
    254     }
    255 
    256     private void cleanupResourceImpl(ResourcesKey removedKey) {
    257         // Remove resource key to resource impl mapping and flush cache
    258         final ResourcesImpl res = mResourceImpls.remove(removedKey).get();
    259 
    260         if (res != null) {
    261             res.flushLayoutCache();
    262         }
    263     }
    264 
    265     /**
    266      * Creates an AssetManager from the paths within the ResourcesKey.
    267      *
    268      * This can be overridden in tests so as to avoid creating a real AssetManager with
    269      * real APK paths.
    270      * @param key The key containing the resource paths to add to the AssetManager.
    271      * @return a new AssetManager.
    272     */
    273     @VisibleForTesting
    274     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
    275         AssetManager assets = new AssetManager();
    276 
    277         // resDir can be null if the 'android' package is creating a new Resources object.
    278         // This is fine, since each AssetManager automatically loads the 'android' package
    279         // already.
    280         if (key.mResDir != null) {
    281             if (assets.addAssetPath(key.mResDir) == 0) {
    282                 Log.e(TAG, "failed to add asset path " + key.mResDir);
    283                 return null;
    284             }
    285         }
    286 
    287         if (key.mSplitResDirs != null) {
    288             for (final String splitResDir : key.mSplitResDirs) {
    289                 if (assets.addAssetPath(splitResDir) == 0) {
    290                     Log.e(TAG, "failed to add split asset path " + splitResDir);
    291                     return null;
    292                 }
    293             }
    294         }
    295 
    296         if (key.mOverlayDirs != null) {
    297             for (final String idmapPath : key.mOverlayDirs) {
    298                 assets.addOverlayPath(idmapPath);
    299             }
    300         }
    301 
    302         if (key.mLibDirs != null) {
    303             for (final String libDir : key.mLibDirs) {
    304                 if (libDir.endsWith(".apk")) {
    305                     // Avoid opening files we know do not have resources,
    306                     // like code-only .jar files.
    307                     if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
    308                         Log.w(TAG, "Asset path '" + libDir +
    309                                 "' does not exist or contains no resources.");
    310                     }
    311                 }
    312             }
    313         }
    314         return assets;
    315     }
    316 
    317     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
    318         Configuration config;
    319         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
    320         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    321         if (!isDefaultDisplay || hasOverrideConfig) {
    322             config = new Configuration(getConfiguration());
    323             if (!isDefaultDisplay) {
    324                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
    325             }
    326             if (hasOverrideConfig) {
    327                 config.updateFrom(key.mOverrideConfiguration);
    328                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
    329             }
    330         } else {
    331             config = getConfiguration();
    332         }
    333         return config;
    334     }
    335 
    336     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    337         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    338         daj.setCompatibilityInfo(key.mCompatInfo);
    339 
    340         final AssetManager assets = createAssetManager(key);
    341         if (assets == null) {
    342             return null;
    343         }
    344 
    345         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
    346         final Configuration config = generateConfig(key, dm);
    347         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
    348 
    349         if (DEBUG) {
    350             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
    351         }
    352         return impl;
    353     }
    354 
    355     /**
    356      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
    357      *
    358      * @param key The key to match.
    359      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
    360      */
    361     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
    362         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
    363         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    364         if (impl != null && impl.getAssets().isUpToDate()) {
    365             return impl;
    366         }
    367         return null;
    368     }
    369 
    370     /**
    371      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
    372      * creates a new one and caches it for future use.
    373      * @param key The key to match.
    374      * @return a ResourcesImpl object matching the key.
    375      */
    376     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
    377             @NonNull ResourcesKey key) {
    378         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
    379         if (impl == null) {
    380             impl = createResourcesImpl(key);
    381             if (impl != null) {
    382                 mResourceImpls.put(key, new WeakReference<>(impl));
    383             }
    384         }
    385         return impl;
    386     }
    387 
    388     /**
    389      * Find the ResourcesKey that this ResourcesImpl object is associated with.
    390      * @return the ResourcesKey or null if none was found.
    391      */
    392     private @Nullable ResourcesKey findKeyForResourceImplLocked(
    393             @NonNull ResourcesImpl resourceImpl) {
    394         final int refCount = mResourceImpls.size();
    395         for (int i = 0; i < refCount; i++) {
    396             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    397             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    398             if (impl != null && resourceImpl == impl) {
    399                 return mResourceImpls.keyAt(i);
    400             }
    401         }
    402         return null;
    403     }
    404 
    405     /**
    406      * Check if activity resources have same override config as the provided on.
    407      * @param activityToken The Activity that resources should be associated with.
    408      * @param overrideConfig The override configuration to be checked for equality with.
    409      * @return true if activity resources override config matches the provided one or they are both
    410      *         null, false otherwise.
    411      */
    412     boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
    413             @Nullable Configuration overrideConfig) {
    414         synchronized (this) {
    415             final ActivityResources activityResources
    416                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
    417             if (activityResources == null) {
    418                 return overrideConfig == null;
    419             } else {
    420                 return Objects.equals(activityResources.overrideConfig, overrideConfig);
    421             }
    422         }
    423     }
    424 
    425     private ActivityResources getOrCreateActivityResourcesStructLocked(
    426             @NonNull IBinder activityToken) {
    427         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
    428         if (activityResources == null) {
    429             activityResources = new ActivityResources();
    430             mActivityResourceReferences.put(activityToken, activityResources);
    431         }
    432         return activityResources;
    433     }
    434 
    435     /**
    436      * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
    437      * or the class loader is different.
    438      */
    439     private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
    440             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
    441             @NonNull CompatibilityInfo compatInfo) {
    442         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
    443                 activityToken);
    444 
    445         final int refCount = activityResources.activityResources.size();
    446         for (int i = 0; i < refCount; i++) {
    447             WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
    448             Resources resources = weakResourceRef.get();
    449 
    450             if (resources != null
    451                     && Objects.equals(resources.getClassLoader(), classLoader)
    452                     && resources.getImpl() == impl) {
    453                 if (DEBUG) {
    454                     Slog.d(TAG, "- using existing ref=" + resources);
    455                 }
    456                 return resources;
    457             }
    458         }
    459 
    460         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
    461                 : new Resources(classLoader);
    462         resources.setImpl(impl);
    463         activityResources.activityResources.add(new WeakReference<>(resources));
    464         if (DEBUG) {
    465             Slog.d(TAG, "- creating new ref=" + resources);
    466             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
    467         }
    468         return resources;
    469     }
    470 
    471     /**
    472      * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
    473      * otherwise creates a new Resources object.
    474      */
    475     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
    476             @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
    477         // Find an existing Resources that has this ResourcesImpl set.
    478         final int refCount = mResourceReferences.size();
    479         for (int i = 0; i < refCount; i++) {
    480             WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
    481             Resources resources = weakResourceRef.get();
    482             if (resources != null &&
    483                     Objects.equals(resources.getClassLoader(), classLoader) &&
    484                     resources.getImpl() == impl) {
    485                 if (DEBUG) {
    486                     Slog.d(TAG, "- using existing ref=" + resources);
    487                 }
    488                 return resources;
    489             }
    490         }
    491 
    492         // Create a new Resources reference and use the existing ResourcesImpl object.
    493         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
    494                 : new Resources(classLoader);
    495         resources.setImpl(impl);
    496         mResourceReferences.add(new WeakReference<>(resources));
    497         if (DEBUG) {
    498             Slog.d(TAG, "- creating new ref=" + resources);
    499             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
    500         }
    501         return resources;
    502     }
    503 
    504     /**
    505      * Creates base resources for an Activity. Calls to
    506      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    507      * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
    508      * configurations merged with the one specified here.
    509      *
    510      * @param activityToken Represents an Activity.
    511      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    512      * @param splitResDirs An array of split resource paths. Can be null.
    513      * @param overlayDirs An array of overlay paths. Can be null.
    514      * @param libDirs An array of resource library paths. Can be null.
    515      * @param displayId The ID of the display for which to create the resources.
    516      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    517      *                       null. This provides the base override for this Activity.
    518      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    519      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    520      * @param classLoader The class loader to use when inflating Resources. If null, the
    521      *                    {@link ClassLoader#getSystemClassLoader()} is used.
    522      * @return a Resources object from which to access resources.
    523      */
    524     public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
    525             @Nullable String resDir,
    526             @Nullable String[] splitResDirs,
    527             @Nullable String[] overlayDirs,
    528             @Nullable String[] libDirs,
    529             int displayId,
    530             @Nullable Configuration overrideConfig,
    531             @NonNull CompatibilityInfo compatInfo,
    532             @Nullable ClassLoader classLoader) {
    533         try {
    534             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    535                     "ResourcesManager#createBaseActivityResources");
    536             final ResourcesKey key = new ResourcesKey(
    537                     resDir,
    538                     splitResDirs,
    539                     overlayDirs,
    540                     libDirs,
    541                     displayId,
    542                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    543                     compatInfo);
    544             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    545 
    546             if (DEBUG) {
    547                 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
    548                         + " with key=" + key);
    549             }
    550 
    551             synchronized (this) {
    552                 // Force the creation of an ActivityResourcesStruct.
    553                 getOrCreateActivityResourcesStructLocked(activityToken);
    554             }
    555 
    556             // Update any existing Activity Resources references.
    557             updateResourcesForActivity(activityToken, overrideConfig, displayId,
    558                     false /* movedToDifferentDisplay */);
    559 
    560             // Now request an actual Resources object.
    561             return getOrCreateResources(activityToken, key, classLoader);
    562         } finally {
    563             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    564         }
    565     }
    566 
    567     /**
    568      * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
    569      * or creates one if it doesn't exist.
    570      *
    571      * @param activityToken The Activity this Resources object should be associated with.
    572      * @param key The key describing the parameters of the ResourcesImpl object.
    573      * @param classLoader The classloader to use for the Resources object.
    574      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
    575      * @return A Resources object that gets updated when
    576      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
    577      *         is called.
    578      */
    579     private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
    580             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    581         synchronized (this) {
    582             if (DEBUG) {
    583                 Throwable here = new Throwable();
    584                 here.fillInStackTrace();
    585                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
    586             }
    587 
    588             if (activityToken != null) {
    589                 final ActivityResources activityResources =
    590                         getOrCreateActivityResourcesStructLocked(activityToken);
    591 
    592                 // Clean up any dead references so they don't pile up.
    593                 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
    594                         sEmptyReferencePredicate);
    595 
    596                 // Rebase the key's override config on top of the Activity's base override.
    597                 if (key.hasOverrideConfiguration()
    598                         && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
    599                     final Configuration temp = new Configuration(activityResources.overrideConfig);
    600                     temp.updateFrom(key.mOverrideConfiguration);
    601                     key.mOverrideConfiguration.setTo(temp);
    602                 }
    603 
    604                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    605                 if (resourcesImpl != null) {
    606                     if (DEBUG) {
    607                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    608                     }
    609                     return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    610                             resourcesImpl, key.mCompatInfo);
    611                 }
    612 
    613                 // We will create the ResourcesImpl object outside of holding this lock.
    614 
    615             } else {
    616                 // Clean up any dead references so they don't pile up.
    617                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
    618 
    619                 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
    620                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    621                 if (resourcesImpl != null) {
    622                     if (DEBUG) {
    623                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    624                     }
    625                     return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
    626                 }
    627 
    628                 // We will create the ResourcesImpl object outside of holding this lock.
    629             }
    630         }
    631 
    632         // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
    633         ResourcesImpl resourcesImpl = createResourcesImpl(key);
    634         if (resourcesImpl == null) {
    635             return null;
    636         }
    637 
    638         synchronized (this) {
    639             ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
    640             if (existingResourcesImpl != null) {
    641                 if (DEBUG) {
    642                     Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
    643                             + " new impl=" + resourcesImpl);
    644                 }
    645                 resourcesImpl.getAssets().close();
    646                 resourcesImpl = existingResourcesImpl;
    647             } else {
    648                 // Add this ResourcesImpl to the cache.
    649                 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
    650             }
    651 
    652             final Resources resources;
    653             if (activityToken != null) {
    654                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    655                         resourcesImpl, key.mCompatInfo);
    656             } else {
    657                 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
    658             }
    659             return resources;
    660         }
    661     }
    662 
    663     /**
    664      * Gets or creates a new Resources object associated with the IBinder token. References returned
    665      * by this method live as long as the Activity, meaning they can be cached and used by the
    666      * Activity even after a configuration change. If any other parameter is changed
    667      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
    668      * is updated and handed back to the caller. However, changing the class loader will result in a
    669      * new Resources object.
    670      * <p/>
    671      * If activityToken is null, a cached Resources object will be returned if it matches the
    672      * input parameters. Otherwise a new Resources object that satisfies these parameters is
    673      * returned.
    674      *
    675      * @param activityToken Represents an Activity. If null, global resources are assumed.
    676      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    677      * @param splitResDirs An array of split resource paths. Can be null.
    678      * @param overlayDirs An array of overlay paths. Can be null.
    679      * @param libDirs An array of resource library paths. Can be null.
    680      * @param displayId The ID of the display for which to create the resources.
    681      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    682      * null. Mostly used with Activities that are in multi-window which may override width and
    683      * height properties from the base config.
    684      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    685      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    686      * @param classLoader The class loader to use when inflating Resources. If null, the
    687      * {@link ClassLoader#getSystemClassLoader()} is used.
    688      * @return a Resources object from which to access resources.
    689      */
    690     public @Nullable Resources getResources(@Nullable IBinder activityToken,
    691             @Nullable String resDir,
    692             @Nullable String[] splitResDirs,
    693             @Nullable String[] overlayDirs,
    694             @Nullable String[] libDirs,
    695             int displayId,
    696             @Nullable Configuration overrideConfig,
    697             @NonNull CompatibilityInfo compatInfo,
    698             @Nullable ClassLoader classLoader) {
    699         try {
    700             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
    701             final ResourcesKey key = new ResourcesKey(
    702                     resDir,
    703                     splitResDirs,
    704                     overlayDirs,
    705                     libDirs,
    706                     displayId,
    707                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    708                     compatInfo);
    709             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    710             return getOrCreateResources(activityToken, key, classLoader);
    711         } finally {
    712             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    713         }
    714     }
    715 
    716     /**
    717      * Updates an Activity's Resources object with overrideConfig. The Resources object
    718      * that was previously returned by
    719      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    720      * CompatibilityInfo, ClassLoader)} is
    721      * still valid and will have the updated configuration.
    722      * @param activityToken The Activity token.
    723      * @param overrideConfig The configuration override to update.
    724      * @param displayId Id of the display where activity currently resides.
    725      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
    726      */
    727     public void updateResourcesForActivity(@NonNull IBinder activityToken,
    728             @Nullable Configuration overrideConfig, int displayId,
    729             boolean movedToDifferentDisplay) {
    730         try {
    731             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    732                     "ResourcesManager#updateResourcesForActivity");
    733             synchronized (this) {
    734                 final ActivityResources activityResources =
    735                         getOrCreateActivityResourcesStructLocked(activityToken);
    736 
    737                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
    738                         && !movedToDifferentDisplay) {
    739                     // They are the same and no change of display id, no work to do.
    740                     return;
    741                 }
    742 
    743                 // Grab a copy of the old configuration so we can create the delta's of each
    744                 // Resources object associated with this Activity.
    745                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
    746 
    747                 // Update the Activity's base override.
    748                 if (overrideConfig != null) {
    749                     activityResources.overrideConfig.setTo(overrideConfig);
    750                 } else {
    751                     activityResources.overrideConfig.unset();
    752                 }
    753 
    754                 if (DEBUG) {
    755                     Throwable here = new Throwable();
    756                     here.fillInStackTrace();
    757                     Slog.d(TAG, "updating resources override for activity=" + activityToken
    758                             + " from oldConfig="
    759                             + Configuration.resourceQualifierString(oldConfig)
    760                             + " to newConfig="
    761                             + Configuration.resourceQualifierString(
    762                             activityResources.overrideConfig) + " displayId=" + displayId,
    763                             here);
    764                 }
    765 
    766                 final boolean activityHasOverrideConfig =
    767                         !activityResources.overrideConfig.equals(Configuration.EMPTY);
    768 
    769                 // Rebase each Resources associated with this Activity.
    770                 final int refCount = activityResources.activityResources.size();
    771                 for (int i = 0; i < refCount; i++) {
    772                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
    773                             i);
    774                     Resources resources = weakResRef.get();
    775                     if (resources == null) {
    776                         continue;
    777                     }
    778 
    779                     // Extract the ResourcesKey that was last used to create the Resources for this
    780                     // activity.
    781                     final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
    782                     if (oldKey == null) {
    783                         Slog.e(TAG, "can't find ResourcesKey for resources impl="
    784                                 + resources.getImpl());
    785                         continue;
    786                     }
    787 
    788                     // Build the new override configuration for this ResourcesKey.
    789                     final Configuration rebasedOverrideConfig = new Configuration();
    790                     if (overrideConfig != null) {
    791                         rebasedOverrideConfig.setTo(overrideConfig);
    792                     }
    793 
    794                     if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
    795                         // Generate a delta between the old base Activity override configuration and
    796                         // the actual final override configuration that was used to figure out the
    797                         // real delta this Resources object wanted.
    798                         Configuration overrideOverrideConfig = Configuration.generateDelta(
    799                                 oldConfig, oldKey.mOverrideConfiguration);
    800                         rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
    801                     }
    802 
    803                     // Create the new ResourcesKey with the rebased override config.
    804                     final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
    805                             oldKey.mSplitResDirs,
    806                             oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
    807                             rebasedOverrideConfig, oldKey.mCompatInfo);
    808 
    809                     if (DEBUG) {
    810                         Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
    811                                 + " to newKey=" + newKey + ", displayId=" + displayId);
    812                     }
    813 
    814                     ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
    815                     if (resourcesImpl == null) {
    816                         resourcesImpl = createResourcesImpl(newKey);
    817                         if (resourcesImpl != null) {
    818                             mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
    819                         }
    820                     }
    821 
    822                     if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
    823                         // Set the ResourcesImpl, updating it for all users of this Resources
    824                         // object.
    825                         resources.setImpl(resourcesImpl);
    826                     }
    827                 }
    828             }
    829         } finally {
    830             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    831         }
    832     }
    833 
    834     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
    835                                                              @Nullable CompatibilityInfo compat) {
    836         try {
    837             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    838                     "ResourcesManager#applyConfigurationToResourcesLocked");
    839 
    840             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
    841                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
    842                         + mResConfiguration.seq + ", newSeq=" + config.seq);
    843                 return false;
    844             }
    845             int changes = mResConfiguration.updateFrom(config);
    846             // Things might have changed in display manager, so clear the cached displays.
    847             mAdjustedDisplays.clear();
    848 
    849             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
    850 
    851             if (compat != null && (mResCompatibilityInfo == null ||
    852                     !mResCompatibilityInfo.equals(compat))) {
    853                 mResCompatibilityInfo = compat;
    854                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
    855                         | ActivityInfo.CONFIG_SCREEN_SIZE
    856                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
    857             }
    858 
    859             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
    860 
    861             ApplicationPackageManager.configurationChanged();
    862             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
    863 
    864             Configuration tmpConfig = null;
    865 
    866             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
    867                 ResourcesKey key = mResourceImpls.keyAt(i);
    868                 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    869                 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
    870                 if (r != null) {
    871                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
    872                             + r + " config to: " + config);
    873                     int displayId = key.mDisplayId;
    874                     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    875                     DisplayMetrics dm = defaultDisplayMetrics;
    876                     final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
    877                     if (!isDefaultDisplay || hasOverrideConfiguration) {
    878                         if (tmpConfig == null) {
    879                             tmpConfig = new Configuration();
    880                         }
    881                         tmpConfig.setTo(config);
    882 
    883                         // Get new DisplayMetrics based on the DisplayAdjustments given
    884                         // to the ResourcesImpl. Update a copy if the CompatibilityInfo
    885                         // changed, because the ResourcesImpl object will handle the
    886                         // update internally.
    887                         DisplayAdjustments daj = r.getDisplayAdjustments();
    888                         if (compat != null) {
    889                             daj = new DisplayAdjustments(daj);
    890                             daj.setCompatibilityInfo(compat);
    891                         }
    892                         dm = getDisplayMetrics(displayId, daj);
    893 
    894                         if (!isDefaultDisplay) {
    895                             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
    896                         }
    897 
    898                         if (hasOverrideConfiguration) {
    899                             tmpConfig.updateFrom(key.mOverrideConfiguration);
    900                         }
    901                         r.updateConfiguration(tmpConfig, dm, compat);
    902                     } else {
    903                         r.updateConfiguration(config, dm, compat);
    904                     }
    905                     //Slog.i(TAG, "Updated app resources " + v.getKey()
    906                     //        + " " + r + ": " + r.getConfiguration());
    907                 } else {
    908                     //Slog.i(TAG, "Removing old resources " + v.getKey());
    909                     mResourceImpls.removeAt(i);
    910                 }
    911             }
    912 
    913             return changes != 0;
    914         } finally {
    915             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    916         }
    917     }
    918 
    919     /**
    920      * Appends the library asset path to any ResourcesImpl object that contains the main
    921      * assetPath.
    922      * @param assetPath The main asset path for which to add the library asset path.
    923      * @param libAsset The library asset path to add.
    924      */
    925     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
    926         synchronized (this) {
    927             // Record which ResourcesImpl need updating
    928             // (and what ResourcesKey they should update to).
    929             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
    930 
    931             final int implCount = mResourceImpls.size();
    932             for (int i = 0; i < implCount; i++) {
    933                 final ResourcesKey key = mResourceImpls.keyAt(i);
    934                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    935                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    936                 if (impl != null && Objects.equals(key.mResDir, assetPath)) {
    937                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
    938                         final int newLibAssetCount = 1 +
    939                                 (key.mLibDirs != null ? key.mLibDirs.length : 0);
    940                         final String[] newLibAssets = new String[newLibAssetCount];
    941                         if (key.mLibDirs != null) {
    942                             System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
    943                         }
    944                         newLibAssets[newLibAssetCount - 1] = libAsset;
    945 
    946                         updatedResourceKeys.put(impl, new ResourcesKey(
    947                                 key.mResDir,
    948                                 key.mSplitResDirs,
    949                                 key.mOverlayDirs,
    950                                 newLibAssets,
    951                                 key.mDisplayId,
    952                                 key.mOverrideConfiguration,
    953                                 key.mCompatInfo));
    954                     }
    955                 }
    956             }
    957 
    958             redirectResourcesToNewImplLocked(updatedResourceKeys);
    959         }
    960     }
    961 
    962     // TODO(adamlesinski): Make this accept more than just overlay directories.
    963     final void applyNewResourceDirsLocked(@NonNull final String baseCodePath,
    964             @NonNull final String[] newResourceDirs) {
    965         try {
    966             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    967                     "ResourcesManager#applyNewResourceDirsLocked");
    968 
    969             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
    970             final int implCount = mResourceImpls.size();
    971             for (int i = 0; i < implCount; i++) {
    972                 final ResourcesKey key = mResourceImpls.keyAt(i);
    973                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    974                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    975                 if (impl != null && (key.mResDir == null || key.mResDir.equals(baseCodePath))) {
    976                     updatedResourceKeys.put(impl, new ResourcesKey(
    977                             key.mResDir,
    978                             key.mSplitResDirs,
    979                             newResourceDirs,
    980                             key.mLibDirs,
    981                             key.mDisplayId,
    982                             key.mOverrideConfiguration,
    983                             key.mCompatInfo));
    984                 }
    985             }
    986 
    987             invalidatePath("/");
    988 
    989             redirectResourcesToNewImplLocked(updatedResourceKeys);
    990         } finally {
    991             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    992         }
    993     }
    994 
    995     private void redirectResourcesToNewImplLocked(
    996             @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
    997         // Bail early if there is no work to do.
    998         if (updatedResourceKeys.isEmpty()) {
    999             return;
   1000         }
   1001 
   1002         // Update any references to ResourcesImpl that require reloading.
   1003         final int resourcesCount = mResourceReferences.size();
   1004         for (int i = 0; i < resourcesCount; i++) {
   1005             final WeakReference<Resources> ref = mResourceReferences.get(i);
   1006             final Resources r = ref != null ? ref.get() : null;
   1007             if (r != null) {
   1008                 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
   1009                 if (key != null) {
   1010                     final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
   1011                     if (impl == null) {
   1012                         throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
   1013                     }
   1014                     r.setImpl(impl);
   1015                 }
   1016             }
   1017         }
   1018 
   1019         // Update any references to ResourcesImpl that require reloading for each Activity.
   1020         for (ActivityResources activityResources : mActivityResourceReferences.values()) {
   1021             final int resCount = activityResources.activityResources.size();
   1022             for (int i = 0; i < resCount; i++) {
   1023                 final WeakReference<Resources> ref = activityResources.activityResources.get(i);
   1024                 final Resources r = ref != null ? ref.get() : null;
   1025                 if (r != null) {
   1026                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
   1027                     if (key != null) {
   1028                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
   1029                         if (impl == null) {
   1030                             throw new Resources.NotFoundException(
   1031                                     "failed to redirect ResourcesImpl");
   1032                         }
   1033                         r.setImpl(impl);
   1034                     }
   1035                 }
   1036             }
   1037         }
   1038     }
   1039 }
   1040