• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.view.Display.DEFAULT_DISPLAY;
21 import static android.view.Display.INVALID_DISPLAY;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.compat.annotation.UnsupportedAppUsage;
26 import android.content.pm.ActivityInfo;
27 import android.content.pm.ApplicationInfo;
28 import android.content.res.ApkAssets;
29 import android.content.res.AssetManager;
30 import android.content.res.CompatResources;
31 import android.content.res.CompatibilityInfo;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.content.res.ResourcesImpl;
35 import android.content.res.ResourcesKey;
36 import android.content.res.loader.ResourcesLoader;
37 import android.hardware.display.DisplayManagerGlobal;
38 import android.os.IBinder;
39 import android.os.Process;
40 import android.os.Trace;
41 import android.util.ArrayMap;
42 import android.util.ArraySet;
43 import android.util.DisplayMetrics;
44 import android.util.Log;
45 import android.util.Pair;
46 import android.util.Slog;
47 import android.view.Display;
48 import android.view.DisplayAdjustments;
49 import android.view.DisplayInfo;
50 import android.window.WindowContext;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.util.ArrayUtils;
54 import com.android.internal.util.IndentingPrintWriter;
55 
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.lang.ref.Reference;
59 import java.lang.ref.ReferenceQueue;
60 import java.lang.ref.WeakReference;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.WeakHashMap;
68 import java.util.function.Function;
69 
70 /** @hide */
71 public class ResourcesManager {
72     static final String TAG = "ResourcesManager";
73     private static final boolean DEBUG = false;
74 
75     private static ResourcesManager sResourcesManager;
76 
77     /**
78      * Internal lock object
79      */
80     private final Object mLock = new Object();
81 
82     /**
83      * The global compatibility settings.
84      */
85     private CompatibilityInfo mResCompatibilityInfo;
86 
87     /**
88      * The global configuration upon which all Resources are based. Multi-window Resources
89      * apply their overrides to this configuration.
90      */
91     @UnsupportedAppUsage
92     private final Configuration mResConfiguration = new Configuration();
93 
94     /**
95      * The display upon which all Resources are based. Activity, window token, and display context
96      * resources apply their overrides to this display id.
97      */
98     private int mResDisplayId = DEFAULT_DISPLAY;
99 
100     /**
101      * ApplicationInfo changes that need to be applied to Resources when the next configuration
102      * change occurs.
103      */
104     private ArrayList<Pair<String[], ApplicationInfo>> mPendingAppInfoUpdates;
105 
106     /**
107      * A mapping of ResourceImpls and their configurations. These are heavy weight objects
108      * which should be reused as much as possible.
109      */
110     @UnsupportedAppUsage
111     private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
112             new ArrayMap<>();
113 
114     /**
115      * A list of Resource references that can be reused.
116      */
117     @UnsupportedAppUsage
118     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
119     private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
120 
121     private static class ApkKey {
122         public final String path;
123         public final boolean sharedLib;
124         public final boolean overlay;
125 
ApkKey(String path, boolean sharedLib, boolean overlay)126         ApkKey(String path, boolean sharedLib, boolean overlay) {
127             this.path = path;
128             this.sharedLib = sharedLib;
129             this.overlay = overlay;
130         }
131 
132         @Override
hashCode()133         public int hashCode() {
134             int result = 1;
135             result = 31 * result + this.path.hashCode();
136             result = 31 * result + Boolean.hashCode(this.sharedLib);
137             result = 31 * result + Boolean.hashCode(this.overlay);
138             return result;
139         }
140 
141         @Override
equals(@ullable Object obj)142         public boolean equals(@Nullable Object obj) {
143             if (!(obj instanceof ApkKey)) {
144                 return false;
145             }
146             ApkKey other = (ApkKey) obj;
147             return this.path.equals(other.path) && this.sharedLib == other.sharedLib
148                     && this.overlay == other.overlay;
149         }
150     }
151 
152     /**
153      * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the
154      * instance is alive and reachable.
155      */
156     private class ApkAssetsSupplier {
157         final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>();
158 
159         /**
160          * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets
161          * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets}
162          * cache.
163          */
load(final ApkKey apkKey)164         ApkAssets load(final ApkKey apkKey) throws IOException {
165             ApkAssets apkAssets = mLocalCache.get(apkKey);
166             if (apkAssets == null) {
167                 apkAssets = loadApkAssets(apkKey);
168                 mLocalCache.put(apkKey, apkAssets);
169             }
170             return apkAssets;
171         }
172     }
173 
174     /**
175      * The ApkAssets that are being referenced in the wild that we can reuse.
176      */
177     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
178 
179     /**
180      * Class containing the base configuration override and set of resources associated with an
181      * {@link Activity} or a {@link WindowContext}.
182      */
183     private static class ActivityResources {
184         /**
185          * Override config to apply to all resources associated with the token this instance is
186          * based on.
187          *
188          * @see #activityResources
189          * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer,
190          * Configuration, CompatibilityInfo, ClassLoader, List)
191          */
192         public final Configuration overrideConfig = new Configuration();
193 
194         /**
195          * The display to apply to all resources associated with the token this instance is based
196          * on.
197          */
198         public int overrideDisplayId;
199 
200         /** List of {@link ActivityResource} associated with the token this instance is based on. */
201         public final ArrayList<ActivityResource> activityResources = new ArrayList<>();
202 
203         public final ReferenceQueue<Resources> activityResourcesQueue = new ReferenceQueue<>();
204 
205         @UnsupportedAppUsage
ActivityResources()206         private ActivityResources() {}
207 
208         /** Returns the number of live resource references within {@code activityResources}. */
countLiveReferences()209         public int countLiveReferences() {
210             int count = 0;
211             for (int i = 0; i < activityResources.size(); i++) {
212                 WeakReference<Resources> resources = activityResources.get(i).resources;
213                 if (resources != null && resources.get() != null) {
214                     count++;
215                 }
216             }
217             return count;
218         }
219     }
220 
221     /**
222      * Contains a resource derived from an {@link Activity} or {@link WindowContext} and information
223      * about how this resource expects its configuration to differ from the token's.
224      *
225      * @see ActivityResources
226      */
227     // TODO: Ideally this class should be called something token related, like TokenBasedResource.
228     private static class ActivityResource {
229         /**
230          * The override configuration applied on top of the token's override config for this
231          * resource.
232          */
233         public final Configuration overrideConfig = new Configuration();
234 
235         /**
236          * If non-null this resource expects its configuration to override the display from the
237          * token's configuration.
238          *
239          * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
240          */
241         @Nullable
242         public Integer overrideDisplayId;
243 
244         @Nullable
245         public WeakReference<Resources> resources;
246 
ActivityResource()247         private ActivityResource() {}
248     }
249 
250     /**
251      * Each Activity or WindowToken may has a base override configuration that is applied to each
252      * Resources object, which in turn may have their own override configuration specified.
253      */
254     @UnsupportedAppUsage
255     private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
256             new WeakHashMap<>();
257 
258     /**
259      * Callback implementation for handling updates to Resources objects.
260      */
261     private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
262 
263     /**
264      * The set of APK paths belonging to this process. This is used to disable incremental
265      * installation crash protections on these APKs so the app either behaves as expects or crashes.
266      */
267     private final ArraySet<String> mApplicationOwnedApks = new ArraySet<>();
268 
269     @UnsupportedAppUsage
ResourcesManager()270     public ResourcesManager() {
271     }
272 
273     @UnsupportedAppUsage
getInstance()274     public static ResourcesManager getInstance() {
275         synchronized (ResourcesManager.class) {
276             if (sResourcesManager == null) {
277                 sResourcesManager = new ResourcesManager();
278             }
279             return sResourcesManager;
280         }
281     }
282 
283     /**
284      * Invalidate and destroy any resources that reference content under the
285      * given filesystem path. Typically used when unmounting a storage device to
286      * try as hard as possible to release any open FDs.
287      */
invalidatePath(String path)288     public void invalidatePath(String path) {
289         synchronized (mLock) {
290             int count = 0;
291 
292             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
293                 final ResourcesKey key = mResourceImpls.keyAt(i);
294                 if (key.isPathReferenced(path)) {
295                     ResourcesImpl impl = mResourceImpls.removeAt(i).get();
296                     if (impl != null) {
297                         impl.flushLayoutCache();
298                     }
299                     count++;
300                 }
301             }
302 
303             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
304 
305             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
306                 final ApkKey key = mCachedApkAssets.keyAt(i);
307                 if (key.path.equals(path)) {
308                     WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
309                     if (apkAssetsRef != null && apkAssetsRef.get() != null) {
310                         apkAssetsRef.get().close();
311                     }
312                 }
313             }
314         }
315     }
316 
getConfiguration()317     public Configuration getConfiguration() {
318         synchronized (mLock) {
319             return mResConfiguration;
320         }
321     }
322 
323     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getDisplayMetrics()324     public DisplayMetrics getDisplayMetrics() {
325         return getDisplayMetrics(mResDisplayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
326     }
327 
328     /**
329      * Protected so that tests can override and returns something a fixed value.
330      */
331     @VisibleForTesting
getDisplayMetrics(int displayId, DisplayAdjustments da)332     protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
333         final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
334         final DisplayMetrics dm = new DisplayMetrics();
335         final DisplayInfo displayInfo = displayManagerGlobal != null
336                 ? displayManagerGlobal.getDisplayInfo(displayId) : null;
337         if (displayInfo != null) {
338             displayInfo.getAppMetrics(dm, da);
339         } else {
340             dm.setToDefaults();
341         }
342         return dm;
343     }
344 
345     /**
346      * Like getDisplayMetrics, but will adjust the result based on the display information in
347      * config. This is used to make sure that the global configuration matches the activity's
348      * apparent display.
349      */
getDisplayMetrics(Configuration config)350     private DisplayMetrics getDisplayMetrics(Configuration config) {
351         final DisplayManagerGlobal displayManagerGlobal = DisplayManagerGlobal.getInstance();
352         final DisplayMetrics dm = new DisplayMetrics();
353         final DisplayInfo displayInfo = displayManagerGlobal != null
354                 ? displayManagerGlobal.getDisplayInfo(mResDisplayId) : null;
355         if (displayInfo != null) {
356             displayInfo.getAppMetrics(dm,
357                     DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS.getCompatibilityInfo(), config);
358         } else {
359             dm.setToDefaults();
360         }
361         return dm;
362     }
363 
applyDisplayMetricsToConfiguration(@onNull DisplayMetrics dm, @NonNull Configuration config)364     private static void applyDisplayMetricsToConfiguration(@NonNull DisplayMetrics dm,
365             @NonNull Configuration config) {
366         config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
367         config.densityDpi = dm.densityDpi;
368         config.screenWidthDp = (int) (dm.widthPixels / dm.density + 0.5f);
369         config.screenHeightDp = (int) (dm.heightPixels / dm.density + 0.5f);
370         int sl = Configuration.resetScreenLayout(config.screenLayout);
371         if (dm.widthPixels > dm.heightPixels) {
372             config.orientation = Configuration.ORIENTATION_LANDSCAPE;
373             config.screenLayout = Configuration.reduceScreenLayout(sl,
374                     config.screenWidthDp, config.screenHeightDp);
375         } else {
376             config.orientation = Configuration.ORIENTATION_PORTRAIT;
377             config.screenLayout = Configuration.reduceScreenLayout(sl,
378                     config.screenHeightDp, config.screenWidthDp);
379         }
380         config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp);
381         config.compatScreenWidthDp = config.screenWidthDp;
382         config.compatScreenHeightDp = config.screenHeightDp;
383         config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
384     }
385 
applyCompatConfiguration(int displayDensity, @NonNull Configuration compatConfiguration)386     public boolean applyCompatConfiguration(int displayDensity,
387             @NonNull Configuration compatConfiguration) {
388         synchronized (mLock) {
389             if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
390                 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
391                 return true;
392             }
393             return false;
394         }
395     }
396 
397     /**
398      * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
399      * available.
400      *
401      * @param displayId display Id.
402      * @param resources The {@link Resources} backing the display adjustments.
403      */
getAdjustedDisplay(final int displayId, Resources resources)404     public Display getAdjustedDisplay(final int displayId, Resources resources) {
405         synchronized (mLock) {
406             final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
407             if (dm == null) {
408                 // may be null early in system startup
409                 return null;
410             }
411             return dm.getCompatibleDisplay(displayId, resources);
412         }
413     }
414 
415     /**
416      * Initializes the set of APKs owned by the application running in this process.
417      */
initializeApplicationPaths(@onNull String sourceDir, @Nullable String[] splitDirs)418     public void initializeApplicationPaths(@NonNull String sourceDir,
419             @Nullable String[] splitDirs) {
420         synchronized (mLock) {
421             if (mApplicationOwnedApks.isEmpty()) {
422                 addApplicationPathsLocked(sourceDir, splitDirs);
423             }
424         }
425     }
426 
427     /**
428      * Updates the set of APKs owned by the application running in this process.
429      *
430      * This method only appends to the set of APKs owned by this process because the previous APKs
431      * paths still belong to the application running in this process.
432      */
addApplicationPathsLocked(@onNull String sourceDir, @Nullable String[] splitDirs)433     private void addApplicationPathsLocked(@NonNull String sourceDir,
434             @Nullable String[] splitDirs) {
435         mApplicationOwnedApks.add(sourceDir);
436         if (splitDirs != null) {
437             mApplicationOwnedApks.addAll(Arrays.asList(splitDirs));
438         }
439     }
440 
overlayPathToIdmapPath(String path)441     private static String overlayPathToIdmapPath(String path) {
442         return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
443     }
444 
loadApkAssets(@onNull final ApkKey key)445     private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
446         ApkAssets apkAssets;
447 
448         // Optimistically check if this ApkAssets exists somewhere else.
449         synchronized (mLock) {
450             final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
451             if (apkAssetsRef != null) {
452                 apkAssets = apkAssetsRef.get();
453                 if (apkAssets != null && apkAssets.isUpToDate()) {
454                     return apkAssets;
455                 } else {
456                     // Clean up the reference.
457                     mCachedApkAssets.remove(key);
458                 }
459             }
460         }
461 
462         int flags = 0;
463         if (key.sharedLib) {
464             flags |= ApkAssets.PROPERTY_DYNAMIC;
465         }
466         if (mApplicationOwnedApks.contains(key.path)) {
467             flags |= ApkAssets.PROPERTY_DISABLE_INCREMENTAL_HARDENING;
468         }
469         if (key.overlay) {
470             apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), flags);
471         } else {
472             apkAssets = ApkAssets.loadFromPath(key.path, flags);
473         }
474 
475         synchronized (mLock) {
476             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
477         }
478 
479         return apkAssets;
480     }
481 
482     /**
483      * Retrieves a list of apk keys representing the ApkAssets that should be loaded for
484      * AssetManagers mapped to the {@param key}.
485      */
extractApkKeys(@onNull final ResourcesKey key)486     private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) {
487         final ArrayList<ApkKey> apkKeys = new ArrayList<>();
488 
489         // resDir can be null if the 'android' package is creating a new Resources object.
490         // This is fine, since each AssetManager automatically loads the 'android' package
491         // already.
492         if (key.mResDir != null) {
493             apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/));
494         }
495 
496         if (key.mSplitResDirs != null) {
497             for (final String splitResDir : key.mSplitResDirs) {
498                 apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/));
499             }
500         }
501 
502         if (key.mLibDirs != null) {
503             for (final String libDir : key.mLibDirs) {
504                 // Avoid opening files we know do not have resources, like code-only .jar files.
505                 if (libDir.endsWith(".apk")) {
506                     apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/));
507                 }
508             }
509         }
510 
511         if (key.mOverlayPaths != null) {
512             for (final String idmapPath : key.mOverlayPaths) {
513                 apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/));
514             }
515         }
516 
517         return apkKeys;
518     }
519 
520     /**
521      * Creates an AssetManager from the paths within the ResourcesKey.
522      *
523      * This can be overridden in tests so as to avoid creating a real AssetManager with
524      * real APK paths.
525      * @param key The key containing the resource paths to add to the AssetManager.
526      * @return a new AssetManager.
527     */
528     @VisibleForTesting
529     @UnsupportedAppUsage
createAssetManager(@onNull final ResourcesKey key)530     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
531         return createAssetManager(key, /* apkSupplier */ null);
532     }
533 
534     /**
535      * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets
536      * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using
537      * {@link #loadApkAssets(ApkKey)}.
538      */
createAssetManager(@onNull final ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)539     private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
540             @Nullable ApkAssetsSupplier apkSupplier) {
541         final AssetManager.Builder builder = new AssetManager.Builder();
542 
543         final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
544         for (int i = 0, n = apkKeys.size(); i < n; i++) {
545             final ApkKey apkKey = apkKeys.get(i);
546             try {
547                 builder.addApkAssets(
548                         (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
549             } catch (IOException e) {
550                 if (apkKey.overlay) {
551                     Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e);
552                 } else if (apkKey.sharedLib) {
553                     Log.w(TAG, String.format(
554                             "asset path '%s' does not exist or contains no resources",
555                             apkKey.path), e);
556                 } else {
557                     Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e);
558                     return null;
559                 }
560             }
561         }
562 
563         if (key.mLoaders != null) {
564             for (final ResourcesLoader loader : key.mLoaders) {
565                 builder.addLoader(loader);
566             }
567         }
568 
569         return builder.build();
570     }
571 
countLiveReferences(Collection<WeakReference<T>> collection)572     private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
573         int count = 0;
574         for (WeakReference<T> ref : collection) {
575             final T value = ref != null ? ref.get() : null;
576             if (value != null) {
577                 count++;
578             }
579         }
580         return count;
581     }
582 
583     /**
584      * @hide
585      */
dump(String prefix, PrintWriter printWriter)586     public void dump(String prefix, PrintWriter printWriter) {
587         synchronized (mLock) {
588             IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
589             for (int i = 0; i < prefix.length() / 2; i++) {
590                 pw.increaseIndent();
591             }
592 
593             pw.println("ResourcesManager:");
594             pw.increaseIndent();
595             pw.print("total apks: ");
596             pw.println(countLiveReferences(mCachedApkAssets.values()));
597 
598             pw.print("resources: ");
599 
600             int references = countLiveReferences(mResourceReferences);
601             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
602                 references += activityResources.countLiveReferences();
603             }
604             pw.println(references);
605 
606             pw.print("resource impls: ");
607             pw.println(countLiveReferences(mResourceImpls.values()));
608         }
609     }
610 
generateConfig(@onNull ResourcesKey key)611     private Configuration generateConfig(@NonNull ResourcesKey key) {
612         Configuration config;
613         final boolean hasOverrideConfig = key.hasOverrideConfiguration();
614         if (hasOverrideConfig) {
615             config = new Configuration(getConfiguration());
616             config.updateFrom(key.mOverrideConfiguration);
617             if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
618         } else {
619             config = getConfiguration();
620         }
621         return config;
622     }
623 
generateDisplayId(@onNull ResourcesKey key)624     private int generateDisplayId(@NonNull ResourcesKey key) {
625         return key.mDisplayId != INVALID_DISPLAY ? key.mDisplayId : mResDisplayId;
626     }
627 
createResourcesImpl(@onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)628     private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
629             @Nullable ApkAssetsSupplier apkSupplier) {
630         final AssetManager assets = createAssetManager(key, apkSupplier);
631         if (assets == null) {
632             return null;
633         }
634 
635         final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
636         daj.setCompatibilityInfo(key.mCompatInfo);
637 
638         final Configuration config = generateConfig(key);
639         final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj);
640         final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj);
641 
642         if (DEBUG) {
643             Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
644         }
645         return impl;
646     }
647 
648     /**
649      * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
650      *
651      * @param key The key to match.
652      * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
653      */
findResourcesImplForKeyLocked(@onNull ResourcesKey key)654     private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
655         WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
656         ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
657         if (impl != null && impl.getAssets().isUpToDate()) {
658             return impl;
659         }
660         return null;
661     }
662 
663     /**
664      * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
665      * creates a new one and caches it for future use.
666      * @param key The key to match.
667      * @return a ResourcesImpl object matching the key.
668      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key)669     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
670             @NonNull ResourcesKey key) {
671         return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null);
672     }
673 
674     /**
675      * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to
676      * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl.
677      */
findOrCreateResourcesImplForKeyLocked( @onNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier)678     private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
679             @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
680         ResourcesImpl impl = findResourcesImplForKeyLocked(key);
681         if (impl == null) {
682             impl = createResourcesImpl(key, apkSupplier);
683             if (impl != null) {
684                 mResourceImpls.put(key, new WeakReference<>(impl));
685             }
686         }
687         return impl;
688     }
689 
690     /**
691      * Find the ResourcesKey that this ResourcesImpl object is associated with.
692      * @return the ResourcesKey or null if none was found.
693      */
findKeyForResourceImplLocked( @onNull ResourcesImpl resourceImpl)694     private @Nullable ResourcesKey findKeyForResourceImplLocked(
695             @NonNull ResourcesImpl resourceImpl) {
696         int refCount = mResourceImpls.size();
697         for (int i = 0; i < refCount; i++) {
698             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
699             if (weakImplRef != null && weakImplRef.refersTo(resourceImpl)) {
700                 return mResourceImpls.keyAt(i);
701             }
702         }
703         return null;
704     }
705 
706     /**
707      * Check if activity resources have same override config as the provided on.
708      * @param activityToken The Activity that resources should be associated with.
709      * @param overrideConfig The override configuration to be checked for equality with.
710      * @return true if activity resources override config matches the provided one or they are both
711      *         null, false otherwise.
712      */
isSameResourcesOverrideConfig(@ullable IBinder activityToken, @Nullable Configuration overrideConfig)713     public boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
714             @Nullable Configuration overrideConfig) {
715         synchronized (mLock) {
716             final ActivityResources activityResources
717                     = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
718             if (activityResources == null) {
719                 return overrideConfig == null;
720             } else {
721                 // The two configurations must either be equal or publicly equivalent to be
722                 // considered the same.
723                 return Objects.equals(activityResources.overrideConfig, overrideConfig)
724                         || (overrideConfig != null && activityResources.overrideConfig != null
725                                 && 0 == overrideConfig.diffPublicOnly(
726                                         activityResources.overrideConfig));
727             }
728         }
729     }
730 
getOrCreateActivityResourcesStructLocked( @onNull IBinder activityToken)731     private ActivityResources getOrCreateActivityResourcesStructLocked(
732             @NonNull IBinder activityToken) {
733         ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
734         if (activityResources == null) {
735             activityResources = new ActivityResources();
736             mActivityResourceReferences.put(activityToken, activityResources);
737         }
738         return activityResources;
739     }
740 
741     @Nullable
findResourcesForActivityLocked(@onNull IBinder targetActivityToken, @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader)742     private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
743             @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
744         ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
745                 targetActivityToken);
746 
747         final int size = activityResources.activityResources.size();
748         for (int index = 0; index < size; index++) {
749             ActivityResource activityResource = activityResources.activityResources.get(index);
750             Resources resources = activityResource.resources.get();
751             ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
752                     resources.getImpl());
753 
754             if (key != null
755                     && Objects.equals(resources.getClassLoader(), targetClassLoader)
756                     && Objects.equals(key, targetKey)) {
757                 return resources;
758             }
759         }
760 
761         return null;
762     }
763 
764     @NonNull
createResourcesForActivityLocked(@onNull IBinder activityToken, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)765     private Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
766             @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId,
767             @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
768             @NonNull CompatibilityInfo compatInfo) {
769         final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
770                 activityToken);
771         cleanupReferences(activityResources.activityResources,
772                 activityResources.activityResourcesQueue,
773                 (r) -> r.resources);
774 
775         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
776                 : new Resources(classLoader);
777         resources.setImpl(impl);
778         resources.setCallbacks(mUpdateCallbacks);
779 
780         ActivityResource activityResource = new ActivityResource();
781         activityResource.resources = new WeakReference<>(resources,
782                 activityResources.activityResourcesQueue);
783         activityResource.overrideConfig.setTo(initialOverrideConfig);
784         activityResource.overrideDisplayId = overrideDisplayId;
785         activityResources.activityResources.add(activityResource);
786         if (DEBUG) {
787             Slog.d(TAG, "- creating new ref=" + resources);
788             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
789         }
790         return resources;
791     }
792 
createResourcesLocked(@onNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo)793     private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
794             @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
795         cleanupReferences(mResourceReferences, mResourcesReferencesQueue);
796 
797         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
798                 : new Resources(classLoader);
799         resources.setImpl(impl);
800         resources.setCallbacks(mUpdateCallbacks);
801         mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue));
802         if (DEBUG) {
803             Slog.d(TAG, "- creating new ref=" + resources);
804             Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
805         }
806         return resources;
807     }
808 
809     /**
810      * Creates base resources for a binder token. Calls to
811      *
812      * {@link #getResources(IBinder, String, String[], String[], String[], String[], Integer,
813      * Configuration, CompatibilityInfo, ClassLoader, List)} with the same binder token will have
814      * their override configurations merged with the one specified here.
815      *
816      * @param token Represents an {@link Activity} or {@link WindowContext}.
817      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
818      * @param splitResDirs An array of split resource paths. Can be null.
819      * @param legacyOverlayDirs An array of overlay APK paths. Can be null.
820      * @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
821      * @param libDirs An array of resource library paths. Can be null.
822      * @param displayId The ID of the display for which to create the resources.
823      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
824      *                       {@code null}. This provides the base override for this token.
825      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
826      *                   {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
827      * @param classLoader The class loader to use when inflating Resources. If null, the
828      *                    {@link ClassLoader#getSystemClassLoader()} is used.
829      * @return a Resources object from which to access resources.
830      */
createBaseTokenResources(@onNull IBinder token, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)831     public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
832             @Nullable String resDir,
833             @Nullable String[] splitResDirs,
834             @Nullable String[] legacyOverlayDirs,
835             @Nullable String[] overlayPaths,
836             @Nullable String[] libDirs,
837             int displayId,
838             @Nullable Configuration overrideConfig,
839             @NonNull CompatibilityInfo compatInfo,
840             @Nullable ClassLoader classLoader,
841             @Nullable List<ResourcesLoader> loaders) {
842         try {
843             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
844                     "ResourcesManager#createBaseActivityResources");
845             final ResourcesKey key = new ResourcesKey(
846                     resDir,
847                     splitResDirs,
848                     combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
849                     libDirs,
850                     displayId,
851                     overrideConfig,
852                     compatInfo,
853                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
854             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
855 
856             if (DEBUG) {
857                 Slog.d(TAG, "createBaseActivityResources activity=" + token
858                         + " with key=" + key);
859             }
860 
861             synchronized (mLock) {
862                 // Force the creation of an ActivityResourcesStruct.
863                 getOrCreateActivityResourcesStructLocked(token);
864             }
865 
866             // Update any existing Activity Resources references.
867             updateResourcesForActivity(token, overrideConfig, displayId);
868 
869             synchronized (mLock) {
870                 Resources resources = findResourcesForActivityLocked(token, key,
871                         classLoader);
872                 if (resources != null) {
873                     return resources;
874                 }
875             }
876 
877             // Now request an actual Resources object.
878             return createResourcesForActivity(token, key,
879                     /* initialOverrideConfig */ Configuration.EMPTY, /* overrideDisplayId */ null,
880                     classLoader, /* apkSupplier */ null);
881         } finally {
882             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
883         }
884     }
885 
886     /**
887      * Rebases a key's override config on top of the Activity's base override.
888      *
889      * @param activityToken the token the supplied {@code key} is derived from.
890      * @param key the key to rebase
891      * @param overridesActivityDisplay whether this key is overriding the display from the token
892      */
rebaseKeyForActivity(IBinder activityToken, ResourcesKey key, boolean overridesActivityDisplay)893     private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key,
894             boolean overridesActivityDisplay) {
895         synchronized (mLock) {
896             final ActivityResources activityResources =
897                     getOrCreateActivityResourcesStructLocked(activityToken);
898 
899             if (key.mDisplayId == INVALID_DISPLAY) {
900                 key.mDisplayId = activityResources.overrideDisplayId;
901             }
902 
903             Configuration config;
904             if (key.hasOverrideConfiguration()) {
905                 config = new Configuration(activityResources.overrideConfig);
906                 config.updateFrom(key.mOverrideConfiguration);
907             } else {
908                 config = activityResources.overrideConfig;
909             }
910 
911             if (overridesActivityDisplay
912                     && key.mOverrideConfiguration.windowConfiguration.getAppBounds() == null) {
913                 if (!key.hasOverrideConfiguration()) {
914                     // Make a copy to handle the case where the override config is set to defaults.
915                     config = new Configuration(config);
916                 }
917 
918                 // If this key is overriding the display from the token and the key's
919                 // window config app bounds is null we need to explicitly override this to
920                 // ensure the display adjustments are as expected.
921                 config.windowConfiguration.setAppBounds(null);
922             }
923 
924             key.mOverrideConfiguration.setTo(config);
925         }
926     }
927 
928     /**
929      * Rebases a key's override config with display metrics of the {@code overrideDisplay} paired
930      * with the {code displayAdjustments}.
931      *
932      * @see #applyDisplayMetricsToConfiguration(DisplayMetrics, Configuration)
933      */
rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay)934     private void rebaseKeyForDisplay(ResourcesKey key, int overrideDisplay) {
935         final Configuration temp = new Configuration();
936 
937         DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
938         daj.setCompatibilityInfo(key.mCompatInfo);
939 
940         final DisplayMetrics dm = getDisplayMetrics(overrideDisplay, daj);
941         applyDisplayMetricsToConfiguration(dm, temp);
942 
943         if (key.hasOverrideConfiguration()) {
944             temp.updateFrom(key.mOverrideConfiguration);
945         }
946         key.mOverrideConfiguration.setTo(temp);
947     }
948 
949     /**
950      * Check WeakReferences and remove any dead references so they don't pile up.
951      */
cleanupReferences(ArrayList<WeakReference<T>> references, ReferenceQueue<T> referenceQueue)952     private static <T> void cleanupReferences(ArrayList<WeakReference<T>> references,
953             ReferenceQueue<T> referenceQueue) {
954         cleanupReferences(references, referenceQueue, Function.identity());
955     }
956 
957     /**
958      * Check WeakReferences and remove any dead references so they don't pile up.
959      */
cleanupReferences(ArrayList<C> referenceContainers, ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction)960     private static <C, T> void cleanupReferences(ArrayList<C> referenceContainers,
961             ReferenceQueue<T> referenceQueue, Function<C, WeakReference<T>> unwrappingFunction) {
962         Reference<? extends T> enqueuedRef = referenceQueue.poll();
963         if (enqueuedRef == null) {
964             return;
965         }
966 
967         final HashSet<Reference<? extends T>> deadReferences = new HashSet<>();
968         for (; enqueuedRef != null; enqueuedRef = referenceQueue.poll()) {
969             deadReferences.add(enqueuedRef);
970         }
971 
972         ArrayUtils.unstableRemoveIf(referenceContainers, (refContainer) -> {
973             WeakReference<T> ref = unwrappingFunction.apply(refContainer);
974             return ref == null || deadReferences.contains(ref);
975         });
976     }
977 
978     /**
979      * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key}
980      * into the supplier. This should be done while the lock is not held to prevent performing I/O
981      * while holding the lock.
982      */
createApkAssetsSupplierNotLocked(@onNull ResourcesKey key)983     private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) {
984         Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
985                 "ResourcesManager#createApkAssetsSupplierNotLocked");
986         try {
987             if (DEBUG && Thread.holdsLock(mLock)) {
988                 Slog.w(TAG, "Calling thread " + Thread.currentThread().getName()
989                     + " is holding mLock", new Throwable());
990             }
991 
992             final ApkAssetsSupplier supplier = new ApkAssetsSupplier();
993             final ArrayList<ApkKey> apkKeys = extractApkKeys(key);
994             for (int i = 0, n = apkKeys.size(); i < n; i++) {
995                 final ApkKey apkKey = apkKeys.get(i);
996                 try {
997                     supplier.load(apkKey);
998                 } catch (IOException e) {
999                     Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e);
1000                 }
1001             }
1002             return supplier;
1003         } finally {
1004             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1005         }
1006     }
1007 
1008     /**
1009      * Creates a Resources object set with a ResourcesImpl object matching the given key.
1010      *
1011      * @param key The key describing the parameters of the ResourcesImpl object.
1012      * @param classLoader The classloader to use for the Resources object.
1013      *                    If null, {@link ClassLoader#getSystemClassLoader()} is used.
1014      * @return A Resources object that gets updated when
1015      *         {@link #applyConfigurationToResources(Configuration, CompatibilityInfo)}
1016      *         is called.
1017      */
1018     @Nullable
createResources(@onNull ResourcesKey key, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1019     private Resources createResources(@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
1020             @Nullable ApkAssetsSupplier apkSupplier) {
1021         synchronized (mLock) {
1022             if (DEBUG) {
1023                 Throwable here = new Throwable();
1024                 here.fillInStackTrace();
1025                 Slog.w(TAG, "!! Create resources for key=" + key, here);
1026             }
1027 
1028             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
1029             if (resourcesImpl == null) {
1030                 return null;
1031             }
1032 
1033             return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
1034         }
1035     }
1036 
1037     @Nullable
createResourcesForActivity(@onNull IBinder activityToken, @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig, @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader, @Nullable ApkAssetsSupplier apkSupplier)1038     private Resources createResourcesForActivity(@NonNull IBinder activityToken,
1039             @NonNull ResourcesKey key, @NonNull Configuration initialOverrideConfig,
1040             @Nullable Integer overrideDisplayId, @NonNull ClassLoader classLoader,
1041             @Nullable ApkAssetsSupplier apkSupplier) {
1042         synchronized (mLock) {
1043             if (DEBUG) {
1044                 Throwable here = new Throwable();
1045                 here.fillInStackTrace();
1046                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
1047             }
1048 
1049             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
1050             if (resourcesImpl == null) {
1051                 return null;
1052             }
1053 
1054             return createResourcesForActivityLocked(activityToken, initialOverrideConfig,
1055                     overrideDisplayId, classLoader, resourcesImpl, key.mCompatInfo);
1056         }
1057     }
1058 
1059     /**
1060      * Gets or creates a new Resources object associated with the IBinder token. References returned
1061      * by this method live as long as the Activity, meaning they can be cached and used by the
1062      * Activity even after a configuration change. If any other parameter is changed
1063      * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
1064      * is updated and handed back to the caller. However, changing the class loader will result in a
1065      * new Resources object.
1066      * <p/>
1067      * If activityToken is null, a cached Resources object will be returned if it matches the
1068      * input parameters. Otherwise a new Resources object that satisfies these parameters is
1069      * returned.
1070      *
1071      * @param activityToken Represents an Activity. If null, global resources are assumed.
1072      * @param resDir The base resource path. Can be null (only framework resources will be loaded).
1073      * @param splitResDirs An array of split resource paths. Can be null.
1074      * @param legacyOverlayDirs An array of overlay APK paths. Can be null.
1075      * @param overlayPaths An array of overlay APK and non-APK paths. Can be null.
1076      * @param libDirs An array of resource library paths. Can be null.
1077      * @param overrideDisplayId The ID of the display for which the returned Resources should be
1078      * based. This will cause display-based configuration properties to override those of the base
1079      * Resources for the {@code activityToken}, or the global configuration if {@code activityToken}
1080      * is null.
1081      * @param overrideConfig The configuration to apply on top of the base configuration. Can be
1082      * null. Mostly used with Activities that are in multi-window which may override width and
1083      * height properties from the base config.
1084      * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
1085      * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
1086      * @param classLoader The class loader to use when inflating Resources. If null, the
1087      * {@link ClassLoader#getSystemClassLoader()} is used.
1088      * @return a Resources object from which to access resources.
1089      */
1090     @Nullable
getResources( @ullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, @Nullable String[] legacyOverlayDirs, @Nullable String[] overlayPaths, @Nullable String[] libDirs, @Nullable Integer overrideDisplayId, @Nullable Configuration overrideConfig, @NonNull CompatibilityInfo compatInfo, @Nullable ClassLoader classLoader, @Nullable List<ResourcesLoader> loaders)1091     public Resources getResources(
1092             @Nullable IBinder activityToken,
1093             @Nullable String resDir,
1094             @Nullable String[] splitResDirs,
1095             @Nullable String[] legacyOverlayDirs,
1096             @Nullable String[] overlayPaths,
1097             @Nullable String[] libDirs,
1098             @Nullable Integer overrideDisplayId,
1099             @Nullable Configuration overrideConfig,
1100             @NonNull CompatibilityInfo compatInfo,
1101             @Nullable ClassLoader classLoader,
1102             @Nullable List<ResourcesLoader> loaders) {
1103         try {
1104             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
1105             final ResourcesKey key = new ResourcesKey(
1106                     resDir,
1107                     splitResDirs,
1108                     combinedOverlayPaths(legacyOverlayDirs, overlayPaths),
1109                     libDirs,
1110                     overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY,
1111                     overrideConfig,
1112                     compatInfo,
1113                     loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
1114             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
1115 
1116             // Preload the ApkAssets required by the key to prevent performing heavy I/O while the
1117             // ResourcesManager lock is held.
1118             final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key);
1119 
1120             if (overrideDisplayId != null) {
1121                 rebaseKeyForDisplay(key, overrideDisplayId);
1122             }
1123 
1124             Resources resources;
1125             if (activityToken != null) {
1126                 Configuration initialOverrideConfig = new Configuration(key.mOverrideConfiguration);
1127                 rebaseKeyForActivity(activityToken, key, overrideDisplayId != null);
1128                 resources = createResourcesForActivity(activityToken, key, initialOverrideConfig,
1129                         overrideDisplayId, classLoader, assetsSupplier);
1130             } else {
1131                 resources = createResources(key, classLoader, assetsSupplier);
1132             }
1133             return resources;
1134         } finally {
1135             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1136         }
1137     }
1138 
1139     /**
1140      * Updates an Activity's Resources object with overrideConfig. The Resources object
1141      * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
1142      * String[], String[], Integer, Configuration, CompatibilityInfo, ClassLoader, List)} is still
1143      * valid and will have the updated configuration.
1144      *
1145      * @param activityToken The Activity token.
1146      * @param overrideConfig The configuration override to update.
1147      * @param displayId Id of the display where activity currently resides.
1148      */
updateResourcesForActivity(@onNull IBinder activityToken, @Nullable Configuration overrideConfig, int displayId)1149     public void updateResourcesForActivity(@NonNull IBinder activityToken,
1150             @Nullable Configuration overrideConfig, int displayId) {
1151         try {
1152             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1153                     "ResourcesManager#updateResourcesForActivity");
1154             if (displayId == INVALID_DISPLAY) {
1155                 throw new IllegalArgumentException("displayId can not be INVALID_DISPLAY");
1156             }
1157             synchronized (mLock) {
1158                 final ActivityResources activityResources =
1159                         getOrCreateActivityResourcesStructLocked(activityToken);
1160 
1161                 boolean movedToDifferentDisplay = activityResources.overrideDisplayId != displayId;
1162                 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
1163                         && !movedToDifferentDisplay) {
1164                     // They are the same and no change of display id, no work to do.
1165                     return;
1166                 }
1167 
1168                 // Grab a copy of the old configuration so we can create the delta's of each
1169                 // Resources object associated with this Activity.
1170                 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
1171 
1172                 // Update the Activity's base override.
1173                 if (overrideConfig != null) {
1174                     activityResources.overrideConfig.setTo(overrideConfig);
1175                 } else {
1176                     activityResources.overrideConfig.unset();
1177                 }
1178 
1179                 // Update the Activity's override display id.
1180                 activityResources.overrideDisplayId = displayId;
1181 
1182                 // If a application info update was scheduled to occur in this process but has not
1183                 // occurred yet, apply it now so the resources objects will have updated paths if
1184                 // the assets sequence changed.
1185                 applyAllPendingAppInfoUpdates();
1186 
1187                 if (DEBUG) {
1188                     Throwable here = new Throwable();
1189                     here.fillInStackTrace();
1190                     Slog.d(TAG, "updating resources override for activity=" + activityToken
1191                             + " from oldConfig="
1192                             + Configuration.resourceQualifierString(oldConfig)
1193                             + " to newConfig="
1194                             + Configuration.resourceQualifierString(
1195                             activityResources.overrideConfig) + " displayId=" + displayId,
1196                             here);
1197                 }
1198 
1199 
1200                 // Rebase each Resources associated with this Activity.
1201                 final int refCount = activityResources.activityResources.size();
1202                 for (int i = 0; i < refCount; i++) {
1203                     final ActivityResource activityResource =
1204                             activityResources.activityResources.get(i);
1205 
1206                     final Resources resources = activityResource.resources.get();
1207                     if (resources == null) {
1208                         continue;
1209                     }
1210 
1211                     final ResourcesKey newKey = rebaseActivityOverrideConfig(activityResource,
1212                             overrideConfig, displayId);
1213                     if (newKey == null) {
1214                         continue;
1215                     }
1216 
1217                     // TODO(b/173090263): Improve the performance of AssetManager & ResourcesImpl
1218                     // constructions.
1219                     final ResourcesImpl resourcesImpl =
1220                             findOrCreateResourcesImplForKeyLocked(newKey);
1221                     if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
1222                         // Set the ResourcesImpl, updating it for all users of this Resources
1223                         // object.
1224                         resources.setImpl(resourcesImpl);
1225                     }
1226                 }
1227             }
1228         } finally {
1229             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1230         }
1231     }
1232 
1233     /**
1234      * Rebases an updated override config over any old override config and returns the new one
1235      * that an Activity's Resources should be set to.
1236      */
1237     @Nullable
rebaseActivityOverrideConfig(@onNull ActivityResource activityResource, @Nullable Configuration newOverrideConfig, int displayId)1238     private ResourcesKey rebaseActivityOverrideConfig(@NonNull ActivityResource activityResource,
1239             @Nullable Configuration newOverrideConfig, int displayId) {
1240         final Resources resources = activityResource.resources.get();
1241         if (resources == null) {
1242             return null;
1243         }
1244 
1245         // Extract the ResourcesKey that was last used to create the Resources for this
1246         // activity.
1247         final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1248         if (oldKey == null) {
1249             Slog.e(TAG, "can't find ResourcesKey for resources impl="
1250                     + resources.getImpl());
1251             return null;
1252         }
1253 
1254         // Build the new override configuration for this ResourcesKey.
1255         final Configuration rebasedOverrideConfig = new Configuration();
1256         if (newOverrideConfig != null) {
1257             rebasedOverrideConfig.setTo(newOverrideConfig);
1258         }
1259 
1260         final Integer overrideDisplayId = activityResource.overrideDisplayId;
1261         if (overrideDisplayId != null) {
1262             DisplayAdjustments displayAdjustments = new DisplayAdjustments(rebasedOverrideConfig);
1263             displayAdjustments.getConfiguration().setTo(activityResource.overrideConfig);
1264             displayAdjustments.setCompatibilityInfo(oldKey.mCompatInfo);
1265 
1266             DisplayMetrics dm = getDisplayMetrics(overrideDisplayId, displayAdjustments);
1267             applyDisplayMetricsToConfiguration(dm, rebasedOverrideConfig);
1268         }
1269 
1270         final boolean hasOverrideConfig =
1271                 !activityResource.overrideConfig.equals(Configuration.EMPTY);
1272         if (hasOverrideConfig) {
1273             rebasedOverrideConfig.updateFrom(activityResource.overrideConfig);
1274         }
1275 
1276         if (activityResource.overrideDisplayId != null
1277                 && activityResource.overrideConfig.windowConfiguration.getAppBounds() == null) {
1278             // If this activity resource is overriding the display from the token and the key's
1279             // window config app bounds is null we need to explicitly override this to
1280             // ensure the display adjustments are as expected.
1281             rebasedOverrideConfig.windowConfiguration.setAppBounds(null);
1282         }
1283 
1284         // Ensure the new key keeps the expected override display instead of the new token display.
1285         displayId = overrideDisplayId != null ? overrideDisplayId : displayId;
1286 
1287         // Create the new ResourcesKey with the rebased override config.
1288         final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
1289                 oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs,
1290                 displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
1291 
1292         if (DEBUG) {
1293             Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
1294                     + " to newKey=" + newKey + ", displayId=" + displayId);
1295         }
1296 
1297         return newKey;
1298     }
1299 
appendPendingAppInfoUpdate(@onNull String[] oldSourceDirs, @NonNull ApplicationInfo appInfo)1300     public void appendPendingAppInfoUpdate(@NonNull String[] oldSourceDirs,
1301             @NonNull ApplicationInfo appInfo) {
1302         synchronized (mLock) {
1303             if (mPendingAppInfoUpdates == null) {
1304                 mPendingAppInfoUpdates = new ArrayList<>();
1305             }
1306             // Clear previous app info changes for a package to prevent multiple ResourcesImpl
1307             // recreations when the recreation caused by this update completely overrides the
1308             // previous pending changes.
1309             for (int i = mPendingAppInfoUpdates.size() - 1; i >= 0; i--) {
1310                 if (ArrayUtils.containsAll(oldSourceDirs, mPendingAppInfoUpdates.get(i).first)) {
1311                     mPendingAppInfoUpdates.remove(i);
1312                 }
1313             }
1314             mPendingAppInfoUpdates.add(new Pair<>(oldSourceDirs, appInfo));
1315         }
1316     }
1317 
applyAllPendingAppInfoUpdates()1318     public final void applyAllPendingAppInfoUpdates() {
1319         synchronized (mLock) {
1320             if (mPendingAppInfoUpdates != null) {
1321                 for (int i = 0, n = mPendingAppInfoUpdates.size(); i < n; i++) {
1322                     final Pair<String[], ApplicationInfo> appInfo = mPendingAppInfoUpdates.get(i);
1323                     applyNewResourceDirsLocked(appInfo.first, appInfo.second);
1324                 }
1325                 mPendingAppInfoUpdates = null;
1326             }
1327         }
1328     }
1329 
applyConfigurationToResources(@onNull Configuration config, @Nullable CompatibilityInfo compat)1330     public final boolean applyConfigurationToResources(@NonNull Configuration config,
1331             @Nullable CompatibilityInfo compat) {
1332         synchronized (mLock) {
1333             try {
1334                 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1335                         "ResourcesManager#applyConfigurationToResources");
1336 
1337                 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
1338                     if (DEBUG || DEBUG_CONFIGURATION) {
1339                         Slog.v(TAG, "Skipping new config: curSeq="
1340                                 + mResConfiguration.seq + ", newSeq=" + config.seq);
1341                     }
1342                     return false;
1343                 }
1344 
1345                 int changes = mResConfiguration.updateFrom(config);
1346                 if (compat != null && (mResCompatibilityInfo == null
1347                         || !mResCompatibilityInfo.equals(compat))) {
1348                     mResCompatibilityInfo = compat;
1349                     changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
1350                             | ActivityInfo.CONFIG_SCREEN_SIZE
1351                             | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
1352                 }
1353 
1354                 // If a application info update was scheduled to occur in this process but has not
1355                 // occurred yet, apply it now so the resources objects will have updated paths when
1356                 // the assets sequence changes.
1357                 if ((changes & ActivityInfo.CONFIG_ASSETS_PATHS) != 0) {
1358                     applyAllPendingAppInfoUpdates();
1359                 }
1360 
1361                 final DisplayMetrics displayMetrics = getDisplayMetrics(config);
1362                 Resources.updateSystemConfiguration(config, displayMetrics, compat);
1363 
1364                 ApplicationPackageManager.configurationChanged();
1365 
1366                 Configuration tmpConfig = new Configuration();
1367 
1368                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1369                     ResourcesKey key = mResourceImpls.keyAt(i);
1370                     WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1371                     ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
1372                     if (r != null) {
1373                         applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
1374                     } else {
1375                         mResourceImpls.removeAt(i);
1376                     }
1377                 }
1378 
1379                 return changes != 0;
1380             } finally {
1381                 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1382             }
1383         }
1384     }
1385 
applyConfigurationToResourcesLocked(@onNull Configuration config, @Nullable CompatibilityInfo compat, Configuration tmpConfig, ResourcesKey key, ResourcesImpl resourcesImpl)1386     private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
1387             @Nullable CompatibilityInfo compat, Configuration tmpConfig,
1388             ResourcesKey key, ResourcesImpl resourcesImpl) {
1389         if (DEBUG || DEBUG_CONFIGURATION) {
1390             Slog.v(TAG, "Changing resources "
1391                     + resourcesImpl + " config to: " + config);
1392         }
1393 
1394         tmpConfig.setTo(config);
1395         if (key.hasOverrideConfiguration()) {
1396             tmpConfig.updateFrom(key.mOverrideConfiguration);
1397         }
1398 
1399         // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
1400         // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
1401         // update internally.
1402         DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
1403         if (compat != null) {
1404             daj = new DisplayAdjustments(daj);
1405             daj.setCompatibilityInfo(compat);
1406         }
1407         daj.setConfiguration(tmpConfig);
1408         DisplayMetrics dm = getDisplayMetrics(generateDisplayId(key), daj);
1409 
1410         resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
1411     }
1412 
1413     /**
1414      * Appends the library asset path to any ResourcesImpl object that contains the main
1415      * assetPath.
1416      * @param assetPath The main asset path for which to add the library asset path.
1417      * @param libAsset The library asset path to add.
1418      */
1419     @UnsupportedAppUsage
appendLibAssetForMainAssetPath(String assetPath, String libAsset)1420     public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
1421         appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset });
1422     }
1423 
1424     /**
1425      * Appends the library asset paths to any ResourcesImpl object that contains the main
1426      * assetPath.
1427      * @param assetPath The main asset path for which to add the library asset path.
1428      * @param libAssets The library asset paths to add.
1429      */
appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets)1430     public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
1431         synchronized (mLock) {
1432             // Record which ResourcesImpl need updating
1433             // (and what ResourcesKey they should update to).
1434             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1435 
1436             final int implCount = mResourceImpls.size();
1437             for (int i = 0; i < implCount; i++) {
1438                 final ResourcesKey key = mResourceImpls.keyAt(i);
1439                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1440                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1441                 if (impl != null && Objects.equals(key.mResDir, assetPath)) {
1442                     String[] newLibAssets = key.mLibDirs;
1443                     for (String libAsset : libAssets) {
1444                         newLibAssets =
1445                                 ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
1446                     }
1447 
1448                     if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
1449                         updatedResourceKeys.put(impl, new ResourcesKey(
1450                                 key.mResDir,
1451                                 key.mSplitResDirs,
1452                                 key.mOverlayPaths,
1453                                 newLibAssets,
1454                                 key.mDisplayId,
1455                                 key.mOverrideConfiguration,
1456                                 key.mCompatInfo,
1457                                 key.mLoaders));
1458                     }
1459                 }
1460             }
1461 
1462             redirectResourcesToNewImplLocked(updatedResourceKeys);
1463         }
1464     }
1465 
applyNewResourceDirsLocked(@ullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo)1466     private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
1467             @NonNull final ApplicationInfo appInfo) {
1468         try {
1469             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1470                     "ResourcesManager#applyNewResourceDirsLocked");
1471 
1472             String baseCodePath = appInfo.getBaseCodePath();
1473 
1474             final int myUid = Process.myUid();
1475             String[] newSplitDirs = appInfo.uid == myUid
1476                     ? appInfo.splitSourceDirs
1477                     : appInfo.splitPublicSourceDirs;
1478 
1479             // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
1480             String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
1481             String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs,
1482                     appInfo.overlayPaths);
1483 
1484             if (appInfo.uid == myUid) {
1485                 addApplicationPathsLocked(baseCodePath, copiedSplitDirs);
1486             }
1487 
1488             final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1489             final int implCount = mResourceImpls.size();
1490             for (int i = 0; i < implCount; i++) {
1491                 final ResourcesKey key = mResourceImpls.keyAt(i);
1492                 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1493                 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
1494 
1495                 if (impl == null) {
1496                     continue;
1497                 }
1498 
1499                 if (key.mResDir == null
1500                         || key.mResDir.equals(baseCodePath)
1501                         || ArrayUtils.contains(oldSourceDirs, key.mResDir)) {
1502                     updatedResourceKeys.put(impl, new ResourcesKey(
1503                             baseCodePath,
1504                             copiedSplitDirs,
1505                             copiedResourceDirs,
1506                             key.mLibDirs,
1507                             key.mDisplayId,
1508                             key.mOverrideConfiguration,
1509                             key.mCompatInfo,
1510                             key.mLoaders
1511                     ));
1512                 }
1513             }
1514 
1515             redirectResourcesToNewImplLocked(updatedResourceKeys);
1516         } finally {
1517             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1518         }
1519     }
1520 
1521     /**
1522      * Creates an array with the contents of {@param overlayPaths} and the unique elements of
1523      * {@param resourceDirs}.
1524      *
1525      * {@link ApplicationInfo#resourceDirs} only contains paths of overlays APKs.
1526      * {@link ApplicationInfo#overlayPaths} was created to contain paths of overlay of varying file
1527      * formats. It also contains the contents of {@code resourceDirs} because the order of loaded
1528      * overlays matter. In case {@code resourceDirs} contains overlay APK paths that are not present
1529      * in overlayPaths (perhaps an app inserted an additional overlay path into a
1530      * {@code resourceDirs}), this method is used to combine the contents of {@code resourceDirs}
1531      * that do not exist in {@code overlayPaths}} and {@code overlayPaths}}.
1532      */
1533     @Nullable
combinedOverlayPaths(@ullable String[] resourceDirs, @Nullable String[] overlayPaths)1534     private static String[] combinedOverlayPaths(@Nullable String[] resourceDirs,
1535             @Nullable String[] overlayPaths) {
1536         if (resourceDirs == null) {
1537             return ArrayUtils.cloneOrNull(overlayPaths);
1538         } else if(overlayPaths == null) {
1539             return ArrayUtils.cloneOrNull(resourceDirs);
1540         } else {
1541             final ArrayList<String> paths = new ArrayList<>();
1542             for (final String path : overlayPaths) {
1543                 paths.add(path);
1544             }
1545             for (final String path : resourceDirs) {
1546                 if (!paths.contains(path)) {
1547                     paths.add(path);
1548                 }
1549             }
1550             return paths.toArray(new String[0]);
1551         }
1552     }
1553 
redirectResourcesToNewImplLocked( @onNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys)1554     private void redirectResourcesToNewImplLocked(
1555             @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
1556         // Bail early if there is no work to do.
1557         if (updatedResourceKeys.isEmpty()) {
1558             return;
1559         }
1560 
1561         // Update any references to ResourcesImpl that require reloading.
1562         final int resourcesCount = mResourceReferences.size();
1563         for (int i = 0; i < resourcesCount; i++) {
1564             final WeakReference<Resources> ref = mResourceReferences.get(i);
1565             final Resources r = ref != null ? ref.get() : null;
1566             if (r != null) {
1567                 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1568                 if (key != null) {
1569                     final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1570                     if (impl == null) {
1571                         throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
1572                     }
1573                     r.setImpl(impl);
1574                 }
1575             }
1576         }
1577 
1578         // Update any references to ResourcesImpl that require reloading for each Activity.
1579         for (ActivityResources activityResources : mActivityResourceReferences.values()) {
1580             final int resCount = activityResources.activityResources.size();
1581             for (int i = 0; i < resCount; i++) {
1582                 final ActivityResource activityResource =
1583                         activityResources.activityResources.get(i);
1584                 final Resources r = activityResource != null
1585                         ? activityResource.resources.get() : null;
1586                 if (r != null) {
1587                     final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1588                     if (key != null) {
1589                         final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1590                         if (impl == null) {
1591                             throw new Resources.NotFoundException(
1592                                     "failed to redirect ResourcesImpl");
1593                         }
1594                         r.setImpl(impl);
1595                     }
1596                 }
1597             }
1598         }
1599     }
1600 
1601     private class UpdateHandler implements Resources.UpdateCallbacks {
1602 
1603         /**
1604          * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
1605          * instance uses.
1606          */
1607         @Override
onLoadersChanged(@onNull Resources resources, @NonNull List<ResourcesLoader> newLoader)1608         public void onLoadersChanged(@NonNull Resources resources,
1609                 @NonNull List<ResourcesLoader> newLoader) {
1610             synchronized (mLock) {
1611                 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1612                 if (oldKey == null) {
1613                     throw new IllegalArgumentException("Cannot modify resource loaders of"
1614                             + " ResourcesImpl not registered with ResourcesManager");
1615                 }
1616 
1617                 final ResourcesKey newKey = new ResourcesKey(
1618                         oldKey.mResDir,
1619                         oldKey.mSplitResDirs,
1620                         oldKey.mOverlayPaths,
1621                         oldKey.mLibDirs,
1622                         oldKey.mDisplayId,
1623                         oldKey.mOverrideConfiguration,
1624                         oldKey.mCompatInfo,
1625                         newLoader.toArray(new ResourcesLoader[0]));
1626 
1627                 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
1628                 resources.setImpl(impl);
1629             }
1630         }
1631 
1632         /**
1633          * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
1634          * {@code loader} to apply any changes of the set of {@link ApkAssets}.
1635          **/
1636         @Override
onLoaderUpdated(@onNull ResourcesLoader loader)1637         public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
1638             synchronized (mLock) {
1639                 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
1640                         new ArrayMap<>();
1641 
1642                 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1643                     final ResourcesKey key = mResourceImpls.keyAt(i);
1644                     final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
1645                     if (impl == null || impl.refersTo(null)
1646                             || !ArrayUtils.contains(key.mLoaders, loader)) {
1647                         continue;
1648                     }
1649 
1650                     mResourceImpls.remove(key);
1651                     updatedResourceImplKeys.put(impl.get(), key);
1652                 }
1653 
1654                 redirectResourcesToNewImplLocked(updatedResourceImplKeys);
1655             }
1656         }
1657     }
1658 }
1659