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 @Nullable 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                 Log.e(TAG, "failed to add asset path " + key.mResDir);
    254                 return null;
    255             }
    256         }
    257 
    258         if (key.mSplitResDirs != null) {
    259             for (final String splitResDir : key.mSplitResDirs) {
    260                 if (assets.addAssetPath(splitResDir) == 0) {
    261                     Log.e(TAG, "failed to add split asset path " + splitResDir);
    262                     return null;
    263                 }
    264             }
    265         }
    266 
    267         if (key.mOverlayDirs != null) {
    268             for (final String idmapPath : key.mOverlayDirs) {
    269                 assets.addOverlayPath(idmapPath);
    270             }
    271         }
    272 
    273         if (key.mLibDirs != null) {
    274             for (final String libDir : key.mLibDirs) {
    275                 if (libDir.endsWith(".apk")) {
    276                     // Avoid opening files we know do not have resources,
    277                     // like code-only .jar files.
    278                     if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
    279                         Log.w(TAG, "Asset path '" + libDir +
    280                                 "' does not exist or contains no resources.");
    281                     }
    282                 }
    283             }
    284         }
    285         return assets;
    286     }
    287 
    288     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
    289         Configuration config;
    290         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
    291         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    292         if (!isDefaultDisplay || hasOverrideConfig) {
    293             config = new Configuration(getConfiguration());
    294             if (!isDefaultDisplay) {
    295                 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
    296             }
    297             if (hasOverrideConfig) {
    298                 config.updateFrom(key.mOverrideConfiguration);
    299                 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
    300             }
    301         } else {
    302             config = getConfiguration();
    303         }
    304         return config;
    305     }
    306 
    307     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
    308         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
    309         daj.setCompatibilityInfo(key.mCompatInfo);
    310 
    311         final AssetManager assets = createAssetManager(key);
    312         if (assets == null) {
    313             return null;
    314         }
    315 
    316         final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
    317         final Configuration config = generateConfig(key, dm);
    318         final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
    319         if (DEBUG) {
    320             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
    321         }
    322         return impl;
    323     }
    324 
    325     /**
    326      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
    327      *
    328      * @param key The key to match.
    329      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
    330      */
    331     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
    332         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
    333         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    334         if (impl != null && impl.getAssets().isUpToDate()) {
    335             return impl;
    336         }
    337         return null;
    338     }
    339 
    340     /**
    341      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
    342      * creates a new one and caches it for future use.
    343      * @param key The key to match.
    344      * @return a ResourcesImpl object matching the key.
    345      */
    346     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
    347             @NonNull ResourcesKey key) {
    348         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
    349         if (impl == null) {
    350             impl = createResourcesImpl(key);
    351             if (impl != null) {
    352                 mResourceImpls.put(key, new WeakReference<>(impl));
    353             }
    354         }
    355         return impl;
    356     }
    357 
    358     /**
    359      * Find the ResourcesKey that this ResourcesImpl object is associated with.
    360      * @return the ResourcesKey or null if none was found.
    361      */
    362     private @Nullable ResourcesKey findKeyForResourceImplLocked(
    363             @NonNull ResourcesImpl resourceImpl) {
    364         final int refCount = mResourceImpls.size();
    365         for (int i = 0; i < refCount; i++) {
    366             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
    367             ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
    368             if (impl != null && resourceImpl == impl) {
    369                 return mResourceImpls.keyAt(i);
    370             }
    371         }
    372         return null;
    373     }
    374 
    375     /**
    376      * Check if activity resources have same override config as the provided on.
    377      * @param activityToken The Activity that resources should be associated with.
    378      * @param overrideConfig The override configuration to be checked for equality with.
    379      * @return true if activity resources override config matches the provided one or they are both
    380      *         null, false otherwise.
    381      */
    382     boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
    383             @Nullable Configuration overrideConfig) {
    384         synchronized (this) {
    385             final ActivityResources activityResources
    386                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
    387             if (activityResources == null) {
    388                 return overrideConfig == null;
    389             } else {
    390                 return Objects.equals(activityResources.overrideConfig, overrideConfig);
    391             }
    392         }
    393     }
    394 
    395     private ActivityResources getOrCreateActivityResourcesStructLocked(
    396             @NonNull IBinder activityToken) {
    397         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
    398         if (activityResources == null) {
    399             activityResources = new ActivityResources();
    400             mActivityResourceReferences.put(activityToken, activityResources);
    401         }
    402         return activityResources;
    403     }
    404 
    405     /**
    406      * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
    407      * or the class loader is different.
    408      */
    409     private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
    410             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
    411         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
    412                 activityToken);
    413 
    414         final int refCount = activityResources.activityResources.size();
    415         for (int i = 0; i < refCount; i++) {
    416             WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
    417             Resources resources = weakResourceRef.get();
    418 
    419             if (resources != null
    420                     && Objects.equals(resources.getClassLoader(), classLoader)
    421                     && resources.getImpl() == impl) {
    422                 if (DEBUG) {
    423                     Slog.d(TAG, "- using existing ref=" + resources);
    424                 }
    425                 return resources;
    426             }
    427         }
    428 
    429         Resources resources = new Resources(classLoader);
    430         resources.setImpl(impl);
    431         activityResources.activityResources.add(new WeakReference<>(resources));
    432         if (DEBUG) {
    433             Slog.d(TAG, "- creating new ref=" + resources);
    434             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
    435         }
    436         return resources;
    437     }
    438 
    439     /**
    440      * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
    441      * otherwise creates a new Resources object.
    442      */
    443     private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
    444             @NonNull ResourcesImpl impl) {
    445         // Find an existing Resources that has this ResourcesImpl set.
    446         final int refCount = mResourceReferences.size();
    447         for (int i = 0; i < refCount; i++) {
    448             WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
    449             Resources resources = weakResourceRef.get();
    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         // Create a new Resources reference and use the existing ResourcesImpl object.
    461         Resources resources = new Resources(classLoader);
    462         resources.setImpl(impl);
    463         mResourceReferences.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      * Creates base resources for an Activity. Calls to
    473      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    474      * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
    475      * configurations merged with the one specified here.
    476      *
    477      * @param activityToken Represents an Activity.
    478      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    479      * @param splitResDirs An array of split resource paths. Can be null.
    480      * @param overlayDirs An array of overlay paths. Can be null.
    481      * @param libDirs An array of resource library paths. Can be null.
    482      * @param displayId The ID of the display for which to create the resources.
    483      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    484      *                       null. This provides the base override for this Activity.
    485      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    486      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    487      * @param classLoader The class loader to use when inflating Resources. If null, the
    488      *                    {@link ClassLoader#getSystemClassLoader()} is used.
    489      * @return a Resources object from which to access resources.
    490      */
    491     public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
    492             @Nullable String resDir,
    493             @Nullable String[] splitResDirs,
    494             @Nullable String[] overlayDirs,
    495             @Nullable String[] libDirs,
    496             int displayId,
    497             @Nullable Configuration overrideConfig,
    498             @NonNull CompatibilityInfo compatInfo,
    499             @Nullable ClassLoader classLoader) {
    500         try {
    501             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    502                     "ResourcesManager#createBaseActivityResources");
    503             final ResourcesKey key = new ResourcesKey(
    504                     resDir,
    505                     splitResDirs,
    506                     overlayDirs,
    507                     libDirs,
    508                     displayId,
    509                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    510                     compatInfo);
    511             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    512 
    513             if (DEBUG) {
    514                 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
    515                         + " with key=" + key);
    516             }
    517 
    518             synchronized (this) {
    519                 // Force the creation of an ActivityResourcesStruct.
    520                 getOrCreateActivityResourcesStructLocked(activityToken);
    521             }
    522 
    523             // Update any existing Activity Resources references.
    524             updateResourcesForActivity(activityToken, overrideConfig);
    525 
    526             // Now request an actual Resources object.
    527             return getOrCreateResources(activityToken, key, classLoader);
    528         } finally {
    529             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    530         }
    531     }
    532 
    533     /**
    534      * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
    535      * or creates one if it doesn't exist.
    536      *
    537      * @param activityToken The Activity this Resources object should be associated with.
    538      * @param key The key describing the parameters of the ResourcesImpl object.
    539      * @param classLoader The classloader to use for the Resources object.
    540      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
    541      * @return A Resources object that gets updated when
    542      *         {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
    543      *         is called.
    544      */
    545     private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
    546             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
    547         synchronized (this) {
    548             if (DEBUG) {
    549                 Throwable here = new Throwable();
    550                 here.fillInStackTrace();
    551                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
    552             }
    553 
    554             if (activityToken != null) {
    555                 final ActivityResources activityResources =
    556                         getOrCreateActivityResourcesStructLocked(activityToken);
    557 
    558                 // Clean up any dead references so they don't pile up.
    559                 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
    560                         sEmptyReferencePredicate);
    561 
    562                 // Rebase the key's override config on top of the Activity's base override.
    563                 if (key.hasOverrideConfiguration()
    564                         && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
    565                     final Configuration temp = new Configuration(activityResources.overrideConfig);
    566                     temp.updateFrom(key.mOverrideConfiguration);
    567                     key.mOverrideConfiguration.setTo(temp);
    568                 }
    569 
    570                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    571                 if (resourcesImpl != null) {
    572                     if (DEBUG) {
    573                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    574                     }
    575                     return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    576                             resourcesImpl);
    577                 }
    578 
    579                 // We will create the ResourcesImpl object outside of holding this lock.
    580 
    581             } else {
    582                 // Clean up any dead references so they don't pile up.
    583                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
    584 
    585                 // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
    586                 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
    587                 if (resourcesImpl != null) {
    588                     if (DEBUG) {
    589                         Slog.d(TAG, "- using existing impl=" + resourcesImpl);
    590                     }
    591                     return getOrCreateResourcesLocked(classLoader, resourcesImpl);
    592                 }
    593 
    594                 // We will create the ResourcesImpl object outside of holding this lock.
    595             }
    596         }
    597 
    598         // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
    599         ResourcesImpl resourcesImpl = createResourcesImpl(key);
    600         if (resourcesImpl == null) {
    601             return null;
    602         }
    603 
    604         synchronized (this) {
    605             ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
    606             if (existingResourcesImpl != null) {
    607                 if (DEBUG) {
    608                     Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
    609                             + " new impl=" + resourcesImpl);
    610                 }
    611                 resourcesImpl.getAssets().close();
    612                 resourcesImpl = existingResourcesImpl;
    613             } else {
    614                 // Add this ResourcesImpl to the cache.
    615                 mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
    616             }
    617 
    618             final Resources resources;
    619             if (activityToken != null) {
    620                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
    621                         resourcesImpl);
    622             } else {
    623                 resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
    624             }
    625             return resources;
    626         }
    627     }
    628 
    629     /**
    630      * Gets or creates a new Resources object associated with the IBinder token. References returned
    631      * by this method live as long as the Activity, meaning they can be cached and used by the
    632      * Activity even after a configuration change. If any other parameter is changed
    633      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
    634      * is updated and handed back to the caller. However, changing the class loader will result in a
    635      * new Resources object.
    636      * <p/>
    637      * If activityToken is null, a cached Resources object will be returned if it matches the
    638      * input parameters. Otherwise a new Resources object that satisfies these parameters is
    639      * returned.
    640      *
    641      * @param activityToken Represents an Activity. If null, global resources are assumed.
    642      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
    643      * @param splitResDirs An array of split resource paths. Can be null.
    644      * @param overlayDirs An array of overlay paths. Can be null.
    645      * @param libDirs An array of resource library paths. Can be null.
    646      * @param displayId The ID of the display for which to create the resources.
    647      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
    648      * null. Mostly used with Activities that are in multi-window which may override width and
    649      * height properties from the base config.
    650      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
    651      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
    652      * @param classLoader The class loader to use when inflating Resources. If null, the
    653      * {@link ClassLoader#getSystemClassLoader()} is used.
    654      * @return a Resources object from which to access resources.
    655      */
    656     public @Nullable Resources getResources(@Nullable IBinder activityToken,
    657             @Nullable String resDir,
    658             @Nullable String[] splitResDirs,
    659             @Nullable String[] overlayDirs,
    660             @Nullable String[] libDirs,
    661             int displayId,
    662             @Nullable Configuration overrideConfig,
    663             @NonNull CompatibilityInfo compatInfo,
    664             @Nullable ClassLoader classLoader) {
    665         try {
    666             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
    667             final ResourcesKey key = new ResourcesKey(
    668                     resDir,
    669                     splitResDirs,
    670                     overlayDirs,
    671                     libDirs,
    672                     displayId,
    673                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
    674                     compatInfo);
    675             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    676             return getOrCreateResources(activityToken, key, classLoader);
    677         } finally {
    678             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    679         }
    680     }
    681 
    682     /**
    683      * Updates an Activity's Resources object with overrideConfig. The Resources object
    684      * that was previously returned by
    685      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
    686      * CompatibilityInfo, ClassLoader)} is
    687      * still valid and will have the updated configuration.
    688      * @param activityToken The Activity token.
    689      * @param overrideConfig The configuration override to update.
    690      */
    691     public void updateResourcesForActivity(@NonNull IBinder activityToken,
    692             @Nullable Configuration overrideConfig) {
    693         try {
    694             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    695                     "ResourcesManager#updateResourcesForActivity");
    696             synchronized (this) {
    697                 final ActivityResources activityResources =
    698                         getOrCreateActivityResourcesStructLocked(activityToken);
    699 
    700                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
    701                     // They are the same, no work to do.
    702                     return;
    703                 }
    704 
    705                 // Grab a copy of the old configuration so we can create the delta's of each
    706                 // Resources object associated with this Activity.
    707                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
    708 
    709                 // Update the Activity's base override.
    710                 if (overrideConfig != null) {
    711                     activityResources.overrideConfig.setTo(overrideConfig);
    712                 } else {
    713                     activityResources.overrideConfig.setToDefaults();
    714                 }
    715 
    716                 if (DEBUG) {
    717                     Throwable here = new Throwable();
    718                     here.fillInStackTrace();
    719                     Slog.d(TAG, "updating resources override for activity=" + activityToken
    720                             + " from oldConfig="
    721                             + Configuration.resourceQualifierString(oldConfig)
    722                             + " to newConfig="
    723                             + Configuration.resourceQualifierString(
    724                             activityResources.overrideConfig),
    725                             here);
    726                 }
    727 
    728                 final boolean activityHasOverrideConfig =
    729                         !activityResources.overrideConfig.equals(Configuration.EMPTY);
    730 
    731                 // Rebase each Resources associated with this Activity.
    732                 final int refCount = activityResources.activityResources.size();
    733                 for (int i = 0; i < refCount; i++) {
    734                     WeakReference<Resources> weakResRef = activityResources.activityResources.get(
    735                             i);
    736                     Resources resources = weakResRef.get();
    737                     if (resources == null) {
    738                         continue;
    739                     }
    740 
    741                     // Extract the ResourcesKey that was last used to create the Resources for this
    742                     // activity.
    743                     final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
    744                     if (oldKey == null) {
    745                         Slog.e(TAG, "can't find ResourcesKey for resources impl="
    746                                 + resources.getImpl());
    747                         continue;
    748                     }
    749 
    750                     // Build the new override configuration for this ResourcesKey.
    751                     final Configuration rebasedOverrideConfig = new Configuration();
    752                     if (overrideConfig != null) {
    753                         rebasedOverrideConfig.setTo(overrideConfig);
    754                     }
    755 
    756                     if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
    757                         // Generate a delta between the old base Activity override configuration and
    758                         // the actual final override configuration that was used to figure out the
    759                         // real delta this Resources object wanted.
    760                         Configuration overrideOverrideConfig = Configuration.generateDelta(
    761                                 oldConfig, oldKey.mOverrideConfiguration);
    762                         rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
    763                     }
    764 
    765                     // Create the new ResourcesKey with the rebased override config.
    766                     final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
    767                             oldKey.mSplitResDirs,
    768                             oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
    769                             rebasedOverrideConfig, oldKey.mCompatInfo);
    770 
    771                     if (DEBUG) {
    772                         Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
    773                                 + " to newKey=" + newKey);
    774                     }
    775 
    776                     ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
    777                     if (resourcesImpl == null) {
    778                         resourcesImpl = createResourcesImpl(newKey);
    779                         if (resourcesImpl != null) {
    780                             mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
    781                         }
    782                     }
    783 
    784                     if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
    785                         // Set the ResourcesImpl, updating it for all users of this Resources
    786                         // object.
    787                         resources.setImpl(resourcesImpl);
    788                     }
    789                 }
    790             }
    791         } finally {
    792             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    793         }
    794     }
    795 
    796     public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
    797                                                              @Nullable CompatibilityInfo compat) {
    798         try {
    799             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
    800                     "ResourcesManager#applyConfigurationToResourcesLocked");
    801 
    802             if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
    803                 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
    804                         + mResConfiguration.seq + ", newSeq=" + config.seq);
    805                 return false;
    806             }
    807             int changes = mResConfiguration.updateFrom(config);
    808             // Things might have changed in display manager, so clear the cached displays.
    809             mDisplays.clear();
    810             DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
    811 
    812             if (compat != null && (mResCompatibilityInfo == null ||
    813                     !mResCompatibilityInfo.equals(compat))) {
    814                 mResCompatibilityInfo = compat;
    815                 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
    816                         | ActivityInfo.CONFIG_SCREEN_SIZE
    817                         | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
    818             }
    819 
    820             Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
    821 
    822             ApplicationPackageManager.configurationChanged();
    823             //Slog.i(TAG, "Configuration changed in " + currentPackageName());
    824 
    825             Configuration tmpConfig = null;
    826 
    827             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
    828                 ResourcesKey key = mResourceImpls.keyAt(i);
    829                 ResourcesImpl r = mResourceImpls.valueAt(i).get();
    830                 if (r != null) {
    831                     if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
    832                             + r + " config to: " + config);
    833                     int displayId = key.mDisplayId;
    834                     boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    835                     DisplayMetrics dm = defaultDisplayMetrics;
    836                     final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
    837                     if (!isDefaultDisplay || hasOverrideConfiguration) {
    838                         if (tmpConfig == null) {
    839                             tmpConfig = new Configuration();
    840                         }
    841                         tmpConfig.setTo(config);
    842 
    843                         // Get new DisplayMetrics based on the DisplayAdjustments given
    844                         // to the ResourcesImpl. Update a copy if the CompatibilityInfo
    845                         // changed, because the ResourcesImpl object will handle the
    846                         // update internally.
    847                         DisplayAdjustments daj = r.getDisplayAdjustments();
    848                         if (compat != null) {
    849                             daj = new DisplayAdjustments(daj);
    850                             daj.setCompatibilityInfo(compat);
    851                         }
    852                         dm = getDisplayMetrics(displayId, daj);
    853 
    854                         if (!isDefaultDisplay) {
    855                             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
    856                         }
    857 
    858                         if (hasOverrideConfiguration) {
    859                             tmpConfig.updateFrom(key.mOverrideConfiguration);
    860                         }
    861                         r.updateConfiguration(tmpConfig, dm, compat);
    862                     } else {
    863                         r.updateConfiguration(config, dm, compat);
    864                     }
    865                     //Slog.i(TAG, "Updated app resources " + v.getKey()
    866                     //        + " " + r + ": " + r.getConfiguration());
    867                 } else {
    868                     //Slog.i(TAG, "Removing old resources " + v.getKey());
    869                     mResourceImpls.removeAt(i);
    870                 }
    871             }
    872 
    873             return changes != 0;
    874         } finally {
    875             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
    876         }
    877     }
    878 
    879     /**
    880      * Appends the library asset path to any ResourcesImpl object that contains the main
    881      * assetPath.
    882      * @param assetPath The main asset path for which to add the library asset path.
    883      * @param libAsset The library asset path to add.
    884      */
    885     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
    886         synchronized (this) {
    887             // Record which ResourcesImpl need updating
    888             // (and what ResourcesKey they should update to).
    889             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
    890 
    891             final int implCount = mResourceImpls.size();
    892             for (int i = 0; i < implCount; i++) {
    893                 final ResourcesImpl impl = mResourceImpls.valueAt(i).get();
    894                 final ResourcesKey key = mResourceImpls.keyAt(i);
    895                 if (impl != null && key.mResDir.equals(assetPath)) {
    896                     if (!ArrayUtils.contains(key.mLibDirs, libAsset)) {
    897                         final int newLibAssetCount = 1 +
    898                                 (key.mLibDirs != null ? key.mLibDirs.length : 0);
    899                         final String[] newLibAssets = new String[newLibAssetCount];
    900                         if (key.mLibDirs != null) {
    901                             System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length);
    902                         }
    903                         newLibAssets[newLibAssetCount - 1] = libAsset;
    904 
    905                         updatedResourceKeys.put(impl, new ResourcesKey(
    906                                 key.mResDir,
    907                                 key.mSplitResDirs,
    908                                 key.mOverlayDirs,
    909                                 newLibAssets,
    910                                 key.mDisplayId,
    911                                 key.mOverrideConfiguration,
    912                                 key.mCompatInfo));
    913                     }
    914                 }
    915             }
    916 
    917             // Bail early if there is no work to do.
    918             if (updatedResourceKeys.isEmpty()) {
    919                 return;
    920             }
    921 
    922             // Update any references to ResourcesImpl that require reloading.
    923             final int resourcesCount = mResourceReferences.size();
    924             for (int i = 0; i < resourcesCount; i++) {
    925                 final Resources r = mResourceReferences.get(i).get();
    926                 if (r != null) {
    927                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
    928                     if (key != null) {
    929                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
    930                         if (impl == null) {
    931                             throw new Resources.NotFoundException("failed to load " + libAsset);
    932                         }
    933                         r.setImpl(impl);
    934                     }
    935                 }
    936             }
    937 
    938             // Update any references to ResourcesImpl that require reloading for each Activity.
    939             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
    940                 final int resCount = activityResources.activityResources.size();
    941                 for (int i = 0; i < resCount; i++) {
    942                     final Resources r = activityResources.activityResources.get(i).get();
    943                     if (r != null) {
    944                         final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
    945                         if (key != null) {
    946                             final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
    947                             if (impl == null) {
    948                                 throw new Resources.NotFoundException("failed to load " + libAsset);
    949                             }
    950                             r.setImpl(impl);
    951                         }
    952                     }
    953                 }
    954             }
    955         }
    956     }
    957 }
    958