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