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.CompatibilityInfo;
     26 import android.content.res.Configuration;
     27 import android.content.res.Resources;
     28 import android.content.res.ResourcesImpl;
     29 import android.content.res.ResourcesKey;
     30 import android.hardware.display.DisplayManagerGlobal;
     31 import android.os.IBinder;
     32 import android.os.Trace;
     33 import android.util.ArrayMap;
     34 import android.util.DisplayMetrics;
     35 import android.util.Log;
     36 import android.util.Pair;
     37 import android.util.Slog;
     38 import android.view.Display;
     39 import android.view.DisplayAdjustments;
     40 
     41 import com.android.internal.annotations.VisibleForTesting;
     42 import com.android.internal.util.ArrayUtils;
     43 
     44 import java.lang.ref.WeakReference;
     45 import java.util.ArrayList;
     46 import java.util.Objects;
     47 import java.util.WeakHashMap;
     48 import java.util.function.Predicate;
     49 
     50 /** @hide */
     51 public class ResourcesManager {
     52     static final String TAG = "ResourcesManager";
     53     private static final boolean DEBUG = false;
     54 
     55     private static ResourcesManager sResourcesManager;
     56 
     57     /**
     58      * Predicate that returns true if a WeakReference is gc'ed.
     59      */
     60     private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
     61             new Predicate<WeakReference<Resources>>() {
     62                 @Override
     63                 public boolean test(WeakReference<Resources> weakRef) {
     64                     return weakRef == null || weakRef.get() == null;
     65                 }
     66             };
     67 
     68     /**
     69      * The global compatibility settings.
     70      */
     71     private CompatibilityInfo mResCompatibilityInfo;
     72 
     73     /**
     74      * The global configuration upon which all Resources are based. Multi-window Resources
     75      * apply their overrides to this configuration.
     76      */
     77     private final Configuration mResConfiguration = new Configuration();
     78 
     79     /**
     80      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
     81      * which should be reused as much as possible.
     82      */
     83     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
     84             new ArrayMap<>();
     85 
     86     /**
     87      * A list of Resource references that can be reused.
     88      */
     89     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
     90 
     91     /**
     92      * Resources and base configuration override associated with an Activity.
     93      */
     94     private static class ActivityResources {
     95         public final Configuration overrideConfig = new Configuration();
     96         public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
     97     }
     98 
     99     /**
    100      * Each Activity may has a base override configuration that is applied to each Resources object,
    101      * which in turn may have their own override configuration specified.
    102      */
    103     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
    104             new WeakHashMap<>();
    105 
    106     /**
    107      * A cache of DisplayId to DisplayAdjustments.
    108      */
    109     private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
    110             new ArrayMap<>();
    111 
    112     public static ResourcesManager getInstance() {
    113         synchronized (ResourcesManager.class) {
    114             if (sResourcesManager == null) {
    115                 sResourcesManager = new ResourcesManager();
    116             }
    117             return sResourcesManager;
    118         }
    119     }
    120 
    121     /**
    122      * Invalidate and destroy any resources that reference content under the
    123      * given filesystem path. Typically used when unmounting a storage device to
    124      * try as hard as possible to release any open FDs.
    125      */
    126     public void invalidatePath(String path) {
    127         synchronized (this) {
    128             int count = 0;
    129             for (int i = 0; i < mResourceImpls.size();) {
    130                 final ResourcesKey key = mResourceImpls.keyAt(i);
    131                 if (key.isPathReferenced(path)) {
    132                     final ResourcesImpl res = mResourceImpls.removeAt(i).get();
    133                     if (res != null) {
    134                         res.flushLayoutCache();
    135                     }
    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.
    205      *
    206      * @param displayId display Id.
    207      * @param displayAdjustments display adjustments.
    208      */
    209     public Display getAdjustedDisplay(final int displayId,
    210             @Nullable DisplayAdjustments displayAdjustments) {
    211         final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
    212                 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
    213         final Pair<Integer, DisplayAdjustments> key =
    214                 Pair.create(displayId, displayAdjustmentsCopy);
    215         synchronized (this) {
    216             WeakReference<Display> wd = mDisplays.get(key);
    217             if (wd != null) {
    218                 final Display display = wd.get();
    219                 if (display != null) {
    220                     return display;
    221                 }
    222             }
    223             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
    224             if (dm == null) {
    225                 // may be null early in system startup
    226                 return null;
    227             }
    228             final Display display = dm.getCompatibleDisplay(displayId, key.second);
    229             if (display != null) {
    230                 mDisplays.put(key, new WeakReference<>(display));
    231             }
    232             return display;
    233         }
    234     }
    235 
    236     /**
    237      * Creates an AssetManager from the paths within the ResourcesKey.
    238      *
    239      * This can be overridden in tests so as to avoid creating a real AssetManager with
    240      * real APK paths.
    241      * @param key The key containing the resource paths to add to the AssetManager.
    242      * @return a new AssetManager.
    243     */
    244     @VisibleForTesting
    245     protected @NonNull AssetManager createAssetManager(@NonNull final ResourcesKey key) {
    246         AssetManager assets = new AssetManager();
    247 
    248         // resDir can be null if the 'android' package is creating a new Resources object.
    249         // This is fine, since each AssetManager automatically loads the 'android' package
    250         // already.
    251         if (key.mResDir != null) {
    252             if (assets.addAssetPath(key.mResDir) == 0) {
    253                 throw new Resources.NotFoundException("failed to add asset path " + key.mResDir);
    254             }
    255         }
    256 
    257         if (key.mSplitResDirs != null) {
    258             for (final String splitResDir : key.mSplitResDirs) {
    259                 if (assets.addAssetPath(splitResDir) == 0) {
    260                     throw new Resources.NotFoundException(
    261                             "failed to add split asset path " + splitResDir);
    262                 }
    263             }
    264         }
    265 
    266         if (key.mOverlayDirs != null) {
    267             for (final String idmapPath : key.mOverlayDirs) {
    268                 assets.addOverlayPath(idmapPath);
    269             }
    270         }
    271 
    272         if (key.mLibDirs != null) {
    273             for (final String libDir : key.mLibDirs) {
    274                 if (libDir.endsWith(".apk")) {
    275                     // Avoid opening files we know do not have resources,
    276                     // like code-only .jar files.
    277                     if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
    278                         Log.w(TAG, "Asset path '" + libDir +
    279                                 "' does not exist or contains no resources.");
    280                     }
    281                 }
    282             }
    283         }
    284         return assets;
    285     }
    286 
    287     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
    288         Configuration config;
    289         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
    290         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    291         if (!isDefaultDisplay || hasOverrideConfig) {
    292             config = new Configuration(getConfiguration());
    293             if (!isDefaultDisplay) {
    294                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
    295             }
    296             if (hasOverrideConfig) {
    297                 config.updateFrom(key.mOverrideConfiguration);
    298                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
    299             }
    300         } else {
    301             config = getConfiguration();
    302         }
    303         return config;
    304     }
    305 
    306     private @NonNull ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    307         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    308         daj.setCompatibilityInfo(key.mCompatInfo);
    309 
    310         final AssetManager assets = createAssetManager(key);
    311         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
    312         final Configuration config = generateConfig(key, dm);
    313         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
    314         if (DEBUG) {
    315             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
    316         }
    317         return impl;
    318     }
    319 
    320     /**
    321      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
    322      *
    323      * @param key The key to match.
    324      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
    325      */
    326     private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
    327         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
    328         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    329         if (impl != null && impl.getAssets().isUpToDate()) {
    330             return impl;
    331         }
    332         return null;
    333     }
    334 
    335     /**
    336      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
    337      * creates a new one and caches it for future use.
    338      * @param key The key to match.
    339      * @return a ResourcesImpl object matching the key.
    340      */
    341     private @NonNull ResourcesImpl findOrCreateResourcesImplForKeyLocked(
    342             @NonNull ResourcesKey key) {
    343         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
    344         if (impl == null) {
    345             impl = createResourcesImpl(key);
    346             mResourceImpls.put(key, new WeakReference<>(impl));
    347         }
    348         return impl;
    349     }
    350 
    351     /**
    352      * Find the ResourcesKey that this ResourcesImpl object is associated with.
    353      * @return the ResourcesKey or null if none was found.
    354      */
    355     private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) {
    356         final int refCount = mResourceImpls.size();
    357         for (int i = 0; i < refCount; i++) {
    358             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    359             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    360             if (impl != null && resourceImpl == impl) {
    361                 return mResourceImpls.keyAt(i);
    362             }
    363         }
    364         return null;
    365     }
    366 
    367     private ActivityResources getOrCreateActivityResourcesStructLocked(
    368             @NonNull IBinder activityToken) {
    369         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
    370         if (activityResources == null) {
    371             activityResources = new ActivityResources();
    372             mActivityResourceReferences.put(activityToken, activityResources);
    373         }
    374         return activityResources;
    375     }
    376 
    377     /**
    378      * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
    379      * or the class loader is different.
    380      */
    381     private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
    382             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
    383         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
    384                 activityToken);
    385 
    386         final int refCount = activityResources.activityResources.size();
    387         for (int i = 0; i < refCount; i++) {
    388             WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
    389             Resources resources = weakResourceRef.get();
    390 
    391             if (resources != null
    392                     && Objects.equals(resources.getClassLoader(), classLoader)
    393                     && resources.getImpl() == impl) {
    394                 if (DEBUG) {
    395                     Slog.d(TAG, "- using existing ref=" + resources);
    396                 }
    397                 return resources;
    398             }
    399         }
    400 
    401         Resources resources = new Resources(classLoader);
    402         resources.setImpl(impl);
    403         activityResources.activityResources.add(new WeakReference<>(resources));
    404         if (DEBUG) {
    405             Slog.d(TAG, "- creating new ref=" + resources);
    406             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
    407         }
    408         return resources;
    409     }
    410 
    411     /**
    412      * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
    413      * otherwise creates a new Resources object.
    414      */
    415     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
    416             @NonNull ResourcesImpl impl) {
    417         // Find an existing Resources that has this ResourcesImpl set.
    418         final int refCount = mResourceReferences.size();
    419         for (int i = 0; i < refCount; i++) {
    420             WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
    421             Resources resources = weakResourceRef.get();
    422             if (resources != null &&
    423                     Objects.equals(resources.getClassLoader(), classLoader) &&
    424                     resources.getImpl() == impl) {
    425                 if (DEBUG) {
    426                     Slog.d(TAG, "- using existing ref=" + resources);
    427                 }
    428                 return resources;
    429             }
    430         }
    431 
    432         // Create a new Resources reference and use the existing ResourcesImpl object.
    433         Resources resources = new Resources(classLoader);
    434         resources.setImpl(impl);
    435         mResourceReferences.add(new WeakReference<>(resources));
    436         if (DEBUG) {
    437             Slog.d(TAG, "- creating new ref=" + resources);
    438             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
    439         }
    440         return resources;
    441     }
    442 
    443     /**
    444      * Creates base resources for an Activity. Calls to
    445      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    446      * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
    447      * configurations merged with the one specified here.
    448      *
    449      * @param activityToken Represents an Activity.
    450      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    451      * @param splitResDirs An array of split resource paths. Can be null.
    452      * @param overlayDirs An array of overlay paths. Can be null.
    453      * @param libDirs An array of resource library paths. Can be null.
    454      * @param displayId The ID of the display for which to create the resources.
    455      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    456      *                       null. This provides the base override for this Activity.
    457      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    458      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    459      * @param classLoader The class loader to use when inflating Resources. If null, the
    460      *                    {@link ClassLoader#getSystemClassLoader()} is used.
    461      * @return a Resources object from which to access resources.
    462      */
    463     public @NonNull Resources createBaseActivityResources(@NonNull IBinder activityToken,
    464             @Nullable String resDir,
    465             @Nullable String[] splitResDirs,
    466             @Nullable String[] overlayDirs,
    467             @Nullable String[] libDirs,
    468             int displayId,
    469             @Nullable Configuration overrideConfig,
    470             @NonNull CompatibilityInfo compatInfo,
    471             @Nullable ClassLoader classLoader) {
    472         try {
    473             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    474                     "ResourcesManager#createBaseActivityResources");
    475             final ResourcesKey key = new ResourcesKey(
    476                     resDir,
    477                     splitResDirs,
    478                     overlayDirs,
    479                     libDirs,
    480                     displayId,
    481                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    482                     compatInfo);
    483             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    484 
    485             if (DEBUG) {
    486                 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
    487                         + " with key=" + key);
    488             }
    489 
    490             synchronized (this) {
    491                 // Force the creation of an ActivityResourcesStruct.
    492                 getOrCreateActivityResourcesStructLocked(activityToken);
    493             }
    494 
    495             // Update any existing Activity Resources references.
    496             updateResourcesForActivity(activityToken, overrideConfig);
    497 
    498             // Now request an actual Resources object.
    499             return getOrCreateResources(activityToken, key, classLoader);
    500         } finally {
    501             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    502         }
    503     }
    504 
    505     /**
    506      * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
    507      * or creates one if it doesn't exist.
    508      *
    509      * @param activityToken The Activity this Resources object should be associated with.
    510      * @param key The key describing the parameters of the ResourcesImpl object.
    511      * @param classLoader The classloader to use for the Resources object.
    512      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
    513      * @return A Resources object that gets updated when
    514      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
    515      *         is called.
    516      */
    517     private @NonNull Resources getOrCreateResources(@Nullable IBinder activityToken,
    518             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    519         synchronized (this) {
    520             if (DEBUG) {
    521                 Throwable here = new Throwable();
    522                 here.fillInStackTrace();
    523                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
    524             }
    525 
    526             if (activityToken != null) {
    527                 final ActivityResources activityResources =
    528                         getOrCreateActivityResourcesStructLocked(activityToken);
    529 
    530                 // Clean up any dead references so they don't pile up.
    531                 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
    532                         sEmptyReferencePredicate);
    533 
    534                 // Rebase the key's override config on top of the Activity's base override.
    535                 if (key.hasOverrideConfiguration()
    536                         && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
    537                     final Configuration temp = new Configuration(activityResources.overrideConfig);
    538                     temp.updateFrom(key.mOverrideConfiguration);
    539                     key.mOverrideConfiguration.setTo(temp);
    540                 }
    541 
    542                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    543                 if (resourcesImpl != null) {
    544                     if (DEBUG) {
    545                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    546                     }
    547                     return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    548                             resourcesImpl);
    549                 }
    550 
    551                 // We will create the ResourcesImpl object outside of holding this lock.
    552 
    553             } else {
    554                 // Clean up any dead references so they don't pile up.
    555                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
    556 
    557                 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
    558                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    559                 if (resourcesImpl != null) {
    560                     if (DEBUG) {
    561                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    562                     }
    563                     return getOrCreateResourcesLocked(classLoader, resourcesImpl);
    564                 }
    565 
    566                 // We will create the ResourcesImpl object outside of holding this lock.
    567             }
    568         }
    569 
    570         // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
    571         ResourcesImpl resourcesImpl = createResourcesImpl(key);
    572 
    573         synchronized (this) {
    574             ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
    575             if (existingResourcesImpl != null) {
    576                 if (DEBUG) {
    577                     Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
    578                             + " new impl=" + resourcesImpl);
    579                 }
    580                 resourcesImpl.getAssets().close();
    581                 resourcesImpl = existingResourcesImpl;
    582             } else {
    583                 // Add this ResourcesImpl to the cache.
    584                 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
    585             }
    586 
    587             final Resources resources;
    588             if (activityToken != null) {
    589                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    590                         resourcesImpl);
    591             } else {
    592                 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
    593             }
    594             return resources;
    595         }
    596     }
    597 
    598     /**
    599      * Gets or creates a new Resources object associated with the IBinder token. References returned
    600      * by this method live as long as the Activity, meaning they can be cached and used by the
    601      * Activity even after a configuration change. If any other parameter is changed
    602      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
    603      * is updated and handed back to the caller. However, changing the class loader will result in a
    604      * new Resources object.
    605      * <p/>
    606      * If activityToken is null, a cached Resources object will be returned if it matches the
    607      * input parameters. Otherwise a new Resources object that satisfies these parameters is
    608      * returned.
    609      *
    610      * @param activityToken Represents an Activity. If null, global resources are assumed.
    611      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    612      * @param splitResDirs An array of split resource paths. Can be null.
    613      * @param overlayDirs An array of overlay paths. Can be null.
    614      * @param libDirs An array of resource library paths. Can be null.
    615      * @param displayId The ID of the display for which to create the resources.
    616      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    617      * null. Mostly used with Activities that are in multi-window which may override width and
    618      * height properties from the base config.
    619      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    620      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    621      * @param classLoader The class loader to use when inflating Resources. If null, the
    622      * {@link ClassLoader#getSystemClassLoader()} is used.
    623      * @return a Resources object from which to access resources.
    624      */
    625     public @NonNull Resources getResources(@Nullable IBinder activityToken,
    626             @Nullable String resDir,
    627             @Nullable String[] splitResDirs,
    628             @Nullable String[] overlayDirs,
    629             @Nullable String[] libDirs,
    630             int displayId,
    631             @Nullable Configuration overrideConfig,
    632             @NonNull CompatibilityInfo compatInfo,
    633             @Nullable ClassLoader classLoader) {
    634         try {
    635             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
    636             final ResourcesKey key = new ResourcesKey(
    637                     resDir,
    638                     splitResDirs,
    639                     overlayDirs,
    640                     libDirs,
    641                     displayId,
    642                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    643                     compatInfo);
    644             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    645             return getOrCreateResources(activityToken, key, classLoader);
    646         } finally {
    647             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    648         }
    649     }
    650 
    651     /**
    652      * Updates an Activity's Resources object with overrideConfig. The Resources object
    653      * that was previously returned by
    654      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    655      * CompatibilityInfo, ClassLoader)} is
    656      * still valid and will have the updated configuration.
    657      * @param activityToken The Activity token.
    658      * @param overrideConfig The configuration override to update.
    659      */
    660     public void updateResourcesForActivity(@NonNull IBinder activityToken,
    661             @Nullable Configuration overrideConfig) {
    662         try {
    663             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    664                     "ResourcesManager#updateResourcesForActivity");
    665             synchronized (this) {
    666                 final ActivityResources activityResources =
    667                         getOrCreateActivityResourcesStructLocked(activityToken);
    668 
    669                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
    670                     // They are the same, no work to do.
    671                     return;
    672                 }
    673 
    674                 // Grab a copy of the old configuration so we can create the delta's of each
    675                 // Resources object associated with this Activity.
    676                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
    677 
    678                 // Update the Activity's base override.
    679                 if (overrideConfig != null) {
    680                     activityResources.overrideConfig.setTo(overrideConfig);
    681                 } else {
    682                     activityResources.overrideConfig.setToDefaults();
    683                 }
    684 
    685                 if (DEBUG) {
    686                     Throwable here = new Throwable();
    687                     here.fillInStackTrace();
    688                     Slog.d(TAG, "updating resources override for activity=" + activityToken
    689                             + " from oldConfig="
    690                             + Configuration.resourceQualifierString(oldConfig)
    691                             + " to newConfig="
    692                             + Configuration.resourceQualifierString(
    693                             activityResources.overrideConfig),
    694                             here);
    695                 }
    696 
    697                 final boolean activityHasOverrideConfig =
    698                         !activityResources.overrideConfig.equals(Configuration.EMPTY);
    699 
    700                 // Rebase each Resources associated with this Activity.
    701                 final int refCount = activityResources.activityResources.size();
    702                 for (int i = 0; i < refCount; i++) {
    703                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
    704                             i);
    705                     Resources resources = weakResRef.get();
    706                     if (resources == null) {
    707                         continue;
    708                     }
    709 
    710                     // Extract the ResourcesKey that was last used to create the Resources for this
    711                     // activity.
    712                     final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
    713                     if (oldKey == null) {
    714                         Slog.e(TAG, "can't find ResourcesKey for resources impl="
    715                                 + resources.getImpl());
    716                         continue;
    717                     }
    718 
    719                     // Build the new override configuration for this ResourcesKey.
    720                     final Configuration rebasedOverrideConfig = new Configuration();
    721                     if (overrideConfig != null) {
    722                         rebasedOverrideConfig.setTo(overrideConfig);
    723                     }
    724 
    725                     if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
    726                         // Generate a delta between the old base Activity override configuration and
    727                         // the actual final override configuration that was used to figure out the
    728                         // real delta this Resources object wanted.
    729                         Configuration overrideOverrideConfig = Configuration.generateDelta(
    730                                 oldConfig, oldKey.mOverrideConfiguration);
    731                         rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
    732                     }
    733 
    734                     // Create the new ResourcesKey with the rebased override config.
    735                     final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
    736                             oldKey.mSplitResDirs,
    737                             oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
    738                             rebasedOverrideConfig, oldKey.mCompatInfo);
    739 
    740                     if (DEBUG) {
    741                         Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
    742                                 + " to newKey=" + newKey);
    743                     }
    744 
    745                     ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
    746                     if (resourcesImpl == null) {
    747                         resourcesImpl = createResourcesImpl(newKey);
    748                         mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
    749                     }
    750 
    751                     if (resourcesImpl != resources.getImpl()) {
    752                         // Set the ResourcesImpl, updating it for all users of this Resources
    753                         // object.
    754                         resources.setImpl(resourcesImpl);
    755                     }
    756                 }
    757             }
    758         } finally {
    759             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    760         }
    761     }
    762 
    763     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
    764                                                              @Nullable CompatibilityInfo compat) {
    765         try {
    766             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    767                     "ResourcesManager#applyConfigurationToResourcesLocked");
    768 
    769             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
    770                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
    771                         + mResConfiguration.seq + ", newSeq=" + config.seq);
    772                 return false;
    773             }
    774             int changes = mResConfiguration.updateFrom(config);
    775             // Things might have changed in display manager, so clear the cached displays.
    776             mDisplays.clear();
    777             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
    778 
    779             if (compat != null && (mResCompatibilityInfo == null ||
    780                     !mResCompatibilityInfo.equals(compat))) {
    781                 mResCompatibilityInfo = compat;
    782                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
    783                         | ActivityInfo.CONFIG_SCREEN_SIZE
    784                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
    785             }
    786 
    787             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
    788 
    789             ApplicationPackageManager.configurationChanged();
    790             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
    791 
    792             Configuration tmpConfig = null;
    793 
    794             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
    795                 ResourcesKey key = mResourceImpls.keyAt(i);
    796                 ResourcesImpl r = mResourceImpls.valueAt(i).get();
    797                 if (r != null) {
    798                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
    799                             + r + " config to: " + config);
    800                     int displayId = key.mDisplayId;
    801                     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    802                     DisplayMetrics dm = defaultDisplayMetrics;
    803                     final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
    804                     if (!isDefaultDisplay || hasOverrideConfiguration) {
    805                         if (tmpConfig == null) {
    806                             tmpConfig = new Configuration();
    807                         }
    808                         tmpConfig.setTo(config);
    809                         if (!isDefaultDisplay) {
    810                             // Get new DisplayMetrics based on the DisplayAdjustments given
    811                             // to the ResourcesImpl. Udate a copy if the CompatibilityInfo
    812                             // changed, because the ResourcesImpl object will handle the
    813                             // update internally.
    814                             DisplayAdjustments daj = r.getDisplayAdjustments();
    815                             if (compat != null) {
    816                                 daj = new DisplayAdjustments(daj);
    817                                 daj.setCompatibilityInfo(compat);
    818                             }
    819                             dm = getDisplayMetrics(displayId, daj);
    820                             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
    821                         }
    822                         if (hasOverrideConfiguration) {
    823                             tmpConfig.updateFrom(key.mOverrideConfiguration);
    824                         }
    825                         r.updateConfiguration(tmpConfig, dm, compat);
    826                     } else {
    827                         r.updateConfiguration(config, dm, compat);
    828                     }
    829                     //Slog.i(TAG, "Updated app resources " + v.getKey()
    830                     //        + " " + r + ": " + r.getConfiguration());
    831                 } else {
    832                     //Slog.i(TAG, "Removing old resources " + v.getKey());
    833                     mResourceImpls.removeAt(i);
    834                 }
    835             }
    836 
    837             return changes != 0;
    838         } finally {
    839             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    840         }
    841     }
    842 
    843     /**
    844      * Appends the library asset path to any ResourcesImpl object that contains the main
    845      * assetPath.
    846      * @param assetPath The main asset path for which to add the library asset path.
    847      * @param libAsset The library asset path to add.
    848      */
    849     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
    850         synchronized (this) {
    851             // Record which ResourcesImpl need updating
    852             // (and what ResourcesKey they should update to).
    853             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
    854 
    855             final int implCount = mResourceImpls.size();
    856             for (int i = 0; i < implCount; i++) {
    857                 final ResourcesImpl impl = mResourceImpls.valueAt(i).get();
    858                 final ResourcesKey key = mResourceImpls.keyAt(i);
    859                 if (impl != null && key.mResDir.equals(assetPath)) {
    860                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
    861                         final int newLibAssetCount = 1 +
    862                                 (key.mLibDirs != null ? key.mLibDirs.length : 0);
    863                         final String[] newLibAssets = new String[newLibAssetCount];
    864                         if (key.mLibDirs != null) {
    865                             System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
    866                         }
    867                         newLibAssets[newLibAssetCount - 1] = libAsset;
    868 
    869                         updatedResourceKeys.put(impl, new ResourcesKey(
    870                                 key.mResDir,
    871                                 key.mSplitResDirs,
    872                                 key.mOverlayDirs,
    873                                 newLibAssets,
    874                                 key.mDisplayId,
    875                                 key.mOverrideConfiguration,
    876                                 key.mCompatInfo));
    877                     }
    878                 }
    879             }
    880 
    881             // Bail early if there is no work to do.
    882             if (updatedResourceKeys.isEmpty()) {
    883                 return;
    884             }
    885 
    886             // Update any references to ResourcesImpl that require reloading.
    887             final int resourcesCount = mResourceReferences.size();
    888             for (int i = 0; i < resourcesCount; i++) {
    889                 final Resources r = mResourceReferences.get(i).get();
    890                 if (r != null) {
    891                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
    892                     if (key != null) {
    893                         r.setImpl(findOrCreateResourcesImplForKeyLocked(key));
    894                     }
    895                 }
    896             }
    897 
    898             // Update any references to ResourcesImpl that require reloading for each Activity.
    899             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
    900                 final int resCount = activityResources.activityResources.size();
    901                 for (int i = 0; i < resCount; i++) {
    902                     final Resources r = activityResources.activityResources.get(i).get();
    903                     if (r != null) {
    904                         final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
    905                         if (key != null) {
    906                             r.setImpl(findOrCreateResourcesImplForKeyLocked(key));
    907                         }
    908                     }
    909                 }
    910             }
    911         }
    912     }
    913 }
    914