blob: d09f0bcf427515ce1c72bff155f0c30e432f198b [file] [log] [blame]
Craig Mautner88c05892013-06-28 09:47:45 -07001/*
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
17package android.app;
18
19import static android.app.ActivityThread.DEBUG_CONFIGURATION;
20
Adam Lesinski082614c2016-03-04 14:33:47 -080021import android.annotation.NonNull;
22import android.annotation.Nullable;
Ryan Mitchell4043ca72019-06-03 16:11:24 -070023import android.annotation.TestApi;
Artur Satayevc895b1b2019-12-10 17:47:51 +000024import android.compat.annotation.UnsupportedAppUsage;
Craig Mautner88c05892013-06-28 09:47:45 -070025import android.content.pm.ActivityInfo;
Winsond605e2d2019-02-11 16:05:51 -080026import android.content.pm.ApplicationInfo;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080027import android.content.res.ApkAssets;
Craig Mautner88c05892013-06-28 09:47:45 -070028import android.content.res.AssetManager;
Jason Monkbd60e5b2017-03-02 12:55:00 -050029import android.content.res.CompatResources;
Craig Mautner88c05892013-06-28 09:47:45 -070030import android.content.res.CompatibilityInfo;
31import android.content.res.Configuration;
32import android.content.res.Resources;
Adam Lesinskifb302cc2016-02-29 16:50:38 -080033import android.content.res.ResourcesImpl;
Craig Mautner88c05892013-06-28 09:47:45 -070034import android.content.res.ResourcesKey;
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080035import android.content.res.loader.ResourcesLoader;
Craig Mautner88c05892013-06-28 09:47:45 -070036import android.hardware.display.DisplayManagerGlobal;
Adam Lesinski082614c2016-03-04 14:33:47 -080037import android.os.IBinder;
Winsond605e2d2019-02-11 16:05:51 -080038import android.os.Process;
Adam Lesinskib7e1ce02016-04-11 20:03:01 -070039import android.os.Trace;
Craig Mautner88c05892013-06-28 09:47:45 -070040import android.util.ArrayMap;
41import android.util.DisplayMetrics;
Adam Lesinski1dd50c52015-03-16 15:10:56 -070042import android.util.Log;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080043import android.util.LruCache;
Wale Ogunwale7c726682015-02-06 17:34:28 -080044import android.util.Pair;
Craig Mautner88c05892013-06-28 09:47:45 -070045import android.util.Slog;
46import android.view.Display;
Wale Ogunwale26698512015-06-05 16:55:33 -070047import android.view.DisplayAdjustments;
Jeff Sharkey98bf12f2016-05-26 11:56:57 -060048
Adam Lesinski082614c2016-03-04 14:33:47 -080049import com.android.internal.annotations.VisibleForTesting;
50import com.android.internal.util.ArrayUtils;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080051import com.android.internal.util.IndentingPrintWriter;
Wale Ogunwale26698512015-06-05 16:55:33 -070052
Adam Lesinskibebfcc42018-02-12 14:27:46 -080053import java.io.IOException;
54import java.io.PrintWriter;
Craig Mautner88c05892013-06-28 09:47:45 -070055import java.lang.ref.WeakReference;
Adam Lesinski082614c2016-03-04 14:33:47 -080056import java.util.ArrayList;
Winson9947f1e2019-08-16 10:20:39 -070057import java.util.Arrays;
Adam Lesinskibebfcc42018-02-12 14:27:46 -080058import java.util.Collection;
Winson9947f1e2019-08-16 10:20:39 -070059import java.util.List;
Adam Lesinski082614c2016-03-04 14:33:47 -080060import java.util.Objects;
61import java.util.WeakHashMap;
62import java.util.function.Predicate;
Craig Mautner88c05892013-06-28 09:47:45 -070063
64/** @hide */
65public class ResourcesManager {
66 static final String TAG = "ResourcesManager";
Wale Ogunwale60454db2015-01-23 16:05:07 -080067 private static final boolean DEBUG = false;
Craig Mautner88c05892013-06-28 09:47:45 -070068
69 private static ResourcesManager sResourcesManager;
Adam Lesinski082614c2016-03-04 14:33:47 -080070
71 /**
72 * Predicate that returns true if a WeakReference is gc'ed.
73 */
74 private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
Adam Lesinskibebfcc42018-02-12 14:27:46 -080075 weakRef -> weakRef == null || weakRef.get() == null;
Craig Mautner88c05892013-06-28 09:47:45 -070076
Adam Lesinski082614c2016-03-04 14:33:47 -080077 /**
78 * The global compatibility settings.
79 */
80 private CompatibilityInfo mResCompatibilityInfo;
Craig Mautner88c05892013-06-28 09:47:45 -070081
Adam Lesinski082614c2016-03-04 14:33:47 -080082 /**
83 * The global configuration upon which all Resources are based. Multi-window Resources
84 * apply their overrides to this configuration.
85 */
Mathew Inwood61e8ae62018-08-14 14:17:44 +010086 @UnsupportedAppUsage
Adam Lesinski082614c2016-03-04 14:33:47 -080087 private final Configuration mResConfiguration = new Configuration();
88
89 /**
90 * A mapping of ResourceImpls and their configurations. These are heavy weight objects
91 * which should be reused as much as possible.
92 */
Mathew Inwood61e8ae62018-08-14 14:17:44 +010093 @UnsupportedAppUsage
Adam Lesinski082614c2016-03-04 14:33:47 -080094 private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
95 new ArrayMap<>();
96
97 /**
98 * A list of Resource references that can be reused.
99 */
Mathew Inwood61e8ae62018-08-14 14:17:44 +0100100 @UnsupportedAppUsage
Adam Lesinski082614c2016-03-04 14:33:47 -0800101 private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
102
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800103 private static class ApkKey {
104 public final String path;
105 public final boolean sharedLib;
106 public final boolean overlay;
107
108 ApkKey(String path, boolean sharedLib, boolean overlay) {
109 this.path = path;
110 this.sharedLib = sharedLib;
111 this.overlay = overlay;
112 }
113
114 @Override
115 public int hashCode() {
116 int result = 1;
117 result = 31 * result + this.path.hashCode();
118 result = 31 * result + Boolean.hashCode(this.sharedLib);
119 result = 31 * result + Boolean.hashCode(this.overlay);
120 return result;
121 }
122
123 @Override
124 public boolean equals(Object obj) {
125 if (!(obj instanceof ApkKey)) {
126 return false;
127 }
128 ApkKey other = (ApkKey) obj;
129 return this.path.equals(other.path) && this.sharedLib == other.sharedLib
130 && this.overlay == other.overlay;
131 }
132 }
133
Richard Uhler93090512018-08-20 14:11:37 +0100134 private static final boolean ENABLE_APK_ASSETS_CACHE = false;
Narayan Kamathbc956212018-08-13 12:53:38 +0100135
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800136 /**
137 * The ApkAssets we are caching and intend to hold strong references to.
138 */
Narayan Kamathbc956212018-08-13 12:53:38 +0100139 private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets =
140 (ENABLE_APK_ASSETS_CACHE) ? new LruCache<>(3) : null;
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800141
142 /**
143 * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
144 * in our LRU cache. Bonus resources :)
145 */
146 private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
147
Adam Lesinski082614c2016-03-04 14:33:47 -0800148 /**
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700149 * Resources and base configuration override associated with an Activity.
Adam Lesinski082614c2016-03-04 14:33:47 -0800150 */
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700151 private static class ActivityResources {
Artur Satayev751e5512019-11-15 19:12:49 +0000152 @UnsupportedAppUsage
153 private ActivityResources() {
154 }
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700155 public final Configuration overrideConfig = new Configuration();
156 public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
157 }
158
159 /**
160 * Each Activity may has a base override configuration that is applied to each Resources object,
161 * which in turn may have their own override configuration specified.
162 */
Mathew Inwood61e8ae62018-08-14 14:17:44 +0100163 @UnsupportedAppUsage
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700164 private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
Adam Lesinski082614c2016-03-04 14:33:47 -0800165 new WeakHashMap<>();
166
167 /**
Bryce Lee609bf652017-02-09 16:50:13 -0800168 * A cache of DisplayId, DisplayAdjustments to Display.
Adam Lesinski082614c2016-03-04 14:33:47 -0800169 */
Bryce Lee609bf652017-02-09 16:50:13 -0800170 private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
171 mAdjustedDisplays = new ArrayMap<>();
172
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800173 /**
174 * Callback implementation for handling updates to Resources objects.
175 */
176 private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
177
Mathew Inwood61e8ae62018-08-14 14:17:44 +0100178 @UnsupportedAppUsage
Artur Satayev751e5512019-11-15 19:12:49 +0000179 public ResourcesManager() {
180 }
181
182 @UnsupportedAppUsage
Craig Mautner88c05892013-06-28 09:47:45 -0700183 public static ResourcesManager getInstance() {
184 synchronized (ResourcesManager.class) {
185 if (sResourcesManager == null) {
186 sResourcesManager = new ResourcesManager();
187 }
188 return sResourcesManager;
189 }
190 }
191
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600192 /**
193 * Invalidate and destroy any resources that reference content under the
194 * given filesystem path. Typically used when unmounting a storage device to
195 * try as hard as possible to release any open FDs.
196 */
197 public void invalidatePath(String path) {
198 synchronized (this) {
199 int count = 0;
Winson9947f1e2019-08-16 10:20:39 -0700200
201 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600202 final ResourcesKey key = mResourceImpls.keyAt(i);
203 if (key.isPathReferenced(path)) {
Winson9947f1e2019-08-16 10:20:39 -0700204 ResourcesImpl impl = mResourceImpls.removeAt(i).get();
205 if (impl != null) {
206 impl.flushLayoutCache();
207 }
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600208 count++;
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600209 }
210 }
Winson9947f1e2019-08-16 10:20:39 -0700211
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600212 Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
Ryan Mitchell4043ca72019-06-03 16:11:24 -0700213
214 for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
215 final ApkKey key = mCachedApkAssets.keyAt(i);
216 if (key.path.equals(path)) {
217 WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.remove(key);
218 if (apkAssetsRef != null && apkAssetsRef.get() != null) {
219 apkAssetsRef.get().close();
220 }
221 mCachedApkAssets.remove(key);
222 }
223 }
Jeff Sharkey98bf12f2016-05-26 11:56:57 -0600224 }
225 }
226
Craig Mautner88c05892013-06-28 09:47:45 -0700227 public Configuration getConfiguration() {
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700228 synchronized (this) {
229 return mResConfiguration;
230 }
Craig Mautner88c05892013-06-28 09:47:45 -0700231 }
232
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700233 DisplayMetrics getDisplayMetrics() {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700234 return getDisplayMetrics(Display.DEFAULT_DISPLAY,
235 DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
Craig Mautner88c05892013-06-28 09:47:45 -0700236 }
237
Adam Lesinski082614c2016-03-04 14:33:47 -0800238 /**
239 * Protected so that tests can override and returns something a fixed value.
240 */
241 @VisibleForTesting
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700242 protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
Wale Ogunwale7c726682015-02-06 17:34:28 -0800243 DisplayMetrics dm = new DisplayMetrics();
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700244 final Display display = getAdjustedDisplay(displayId, da);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800245 if (display != null) {
246 display.getMetrics(dm);
Craig Mautner88c05892013-06-28 09:47:45 -0700247 } else {
Craig Mautner88c05892013-06-28 09:47:45 -0700248 dm.setToDefaults();
249 }
Craig Mautner88c05892013-06-28 09:47:45 -0700250 return dm;
251 }
252
Adam Lesinski082614c2016-03-04 14:33:47 -0800253 private static void applyNonDefaultDisplayMetricsToConfiguration(
254 @NonNull DisplayMetrics dm, @NonNull Configuration config) {
Craig Mautner88c05892013-06-28 09:47:45 -0700255 config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
256 config.densityDpi = dm.densityDpi;
Adam Lesinski082614c2016-03-04 14:33:47 -0800257 config.screenWidthDp = (int) (dm.widthPixels / dm.density);
258 config.screenHeightDp = (int) (dm.heightPixels / dm.density);
Craig Mautner88c05892013-06-28 09:47:45 -0700259 int sl = Configuration.resetScreenLayout(config.screenLayout);
260 if (dm.widthPixels > dm.heightPixels) {
261 config.orientation = Configuration.ORIENTATION_LANDSCAPE;
262 config.screenLayout = Configuration.reduceScreenLayout(sl,
263 config.screenWidthDp, config.screenHeightDp);
264 } else {
265 config.orientation = Configuration.ORIENTATION_PORTRAIT;
266 config.screenLayout = Configuration.reduceScreenLayout(sl,
267 config.screenHeightDp, config.screenWidthDp);
268 }
GyeHun Jeon61cac3a2019-04-19 11:30:56 -0700269 config.smallestScreenWidthDp = Math.min(config.screenWidthDp, config.screenHeightDp);
Craig Mautner88c05892013-06-28 09:47:45 -0700270 config.compatScreenWidthDp = config.screenWidthDp;
271 config.compatScreenHeightDp = config.screenHeightDp;
272 config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
273 }
274
Adam Lesinski082614c2016-03-04 14:33:47 -0800275 public boolean applyCompatConfigurationLocked(int displayDensity,
276 @NonNull Configuration compatConfiguration) {
Craig Mautner88c05892013-06-28 09:47:45 -0700277 if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
278 mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
279 return true;
280 }
281 return false;
282 }
283
284 /**
Wale Ogunwale7c726682015-02-06 17:34:28 -0800285 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
Bryce Lee609bf652017-02-09 16:50:13 -0800286 * available. This method is only used within {@link ResourcesManager} to calculate display
287 * metrics based on a set {@link DisplayAdjustments}. All other usages should instead call
288 * {@link ResourcesManager#getAdjustedDisplay(int, Resources)}.
Wale Ogunwale7c726682015-02-06 17:34:28 -0800289 *
290 * @param displayId display Id.
Wale Ogunwale26698512015-06-05 16:55:33 -0700291 * @param displayAdjustments display adjustments.
Wale Ogunwale7c726682015-02-06 17:34:28 -0800292 */
Bryce Lee609bf652017-02-09 16:50:13 -0800293 private Display getAdjustedDisplay(final int displayId,
Adam Lesinski082614c2016-03-04 14:33:47 -0800294 @Nullable DisplayAdjustments displayAdjustments) {
Wale Ogunwale26698512015-06-05 16:55:33 -0700295 final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
296 ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
297 final Pair<Integer, DisplayAdjustments> key =
298 Pair.create(displayId, displayAdjustmentsCopy);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800299 synchronized (this) {
Bryce Lee609bf652017-02-09 16:50:13 -0800300 WeakReference<Display> wd = mAdjustedDisplays.get(key);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800301 if (wd != null) {
302 final Display display = wd.get();
303 if (display != null) {
304 return display;
305 }
306 }
307 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
308 if (dm == null) {
309 // may be null early in system startup
310 return null;
311 }
Wale Ogunwale26698512015-06-05 16:55:33 -0700312 final Display display = dm.getCompatibleDisplay(displayId, key.second);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800313 if (display != null) {
Bryce Lee609bf652017-02-09 16:50:13 -0800314 mAdjustedDisplays.put(key, new WeakReference<>(display));
315 }
316 return display;
317 }
318 }
319
320 /**
321 * Returns an adjusted {@link Display} object based on the inputs or null if display isn't
322 * available.
323 *
324 * @param displayId display Id.
325 * @param resources The {@link Resources} backing the display adjustments.
326 */
327 public Display getAdjustedDisplay(final int displayId, Resources resources) {
Bryce Lee609bf652017-02-09 16:50:13 -0800328 synchronized (this) {
Bryce Lee609bf652017-02-09 16:50:13 -0800329 final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
330 if (dm == null) {
331 // may be null early in system startup
332 return null;
333 }
Andrii Kuliancff2f812017-04-16 19:28:02 -0700334 return dm.getCompatibleDisplay(displayId, resources);
Wale Ogunwale7c726682015-02-06 17:34:28 -0800335 }
336 }
337
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800338 private static String overlayPathToIdmapPath(String path) {
339 return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
340 }
341
342 private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
343 throws IOException {
344 final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
Narayan Kamathbc956212018-08-13 12:53:38 +0100345 ApkAssets apkAssets = null;
346 if (mLoadedApkAssets != null) {
347 apkAssets = mLoadedApkAssets.get(newKey);
348 if (apkAssets != null) {
349 return apkAssets;
350 }
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800351 }
352
353 // Optimistically check if this ApkAssets exists somewhere else.
354 final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
355 if (apkAssetsRef != null) {
356 apkAssets = apkAssetsRef.get();
357 if (apkAssets != null) {
Narayan Kamathbc956212018-08-13 12:53:38 +0100358 if (mLoadedApkAssets != null) {
359 mLoadedApkAssets.put(newKey, apkAssets);
360 }
361
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800362 return apkAssets;
363 } else {
364 // Clean up the reference.
365 mCachedApkAssets.remove(newKey);
366 }
367 }
368
369 // We must load this from disk.
370 if (overlay) {
371 apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
372 false /*system*/);
373 } else {
374 apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
375 }
Narayan Kamathbc956212018-08-13 12:53:38 +0100376
377 if (mLoadedApkAssets != null) {
378 mLoadedApkAssets.put(newKey, apkAssets);
379 }
380
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800381 mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
382 return apkAssets;
383 }
384
Wale Ogunwale7c726682015-02-06 17:34:28 -0800385 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800386 * Creates an AssetManager from the paths within the ResourcesKey.
Craig Mautner88c05892013-06-28 09:47:45 -0700387 *
Adam Lesinski082614c2016-03-04 14:33:47 -0800388 * This can be overridden in tests so as to avoid creating a real AssetManager with
389 * real APK paths.
390 * @param key The key containing the resource paths to add to the AssetManager.
391 * @return a new AssetManager.
392 */
393 @VisibleForTesting
Mathew Inwood61e8ae62018-08-14 14:17:44 +0100394 @UnsupportedAppUsage
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700395 protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800396 final AssetManager.Builder builder = new AssetManager.Builder();
Adam Lesinski082614c2016-03-04 14:33:47 -0800397
Adam Lesinski54130de2014-08-20 10:49:13 -0700398 // resDir can be null if the 'android' package is creating a new Resources object.
399 // This is fine, since each AssetManager automatically loads the 'android' package
400 // already.
Adam Lesinski082614c2016-03-04 14:33:47 -0800401 if (key.mResDir != null) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800402 try {
403 builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
404 false /*overlay*/));
405 } catch (IOException e) {
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700406 Log.e(TAG, "failed to add asset path " + key.mResDir);
407 return null;
Adam Lesinski54130de2014-08-20 10:49:13 -0700408 }
Craig Mautner88c05892013-06-28 09:47:45 -0700409 }
410
Adam Lesinski082614c2016-03-04 14:33:47 -0800411 if (key.mSplitResDirs != null) {
412 for (final String splitResDir : key.mSplitResDirs) {
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800413 try {
414 builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
415 false /*overlay*/));
416 } catch (IOException e) {
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700417 Log.e(TAG, "failed to add split asset path " + splitResDir);
418 return null;
Jeff Sharkey8a4c9722014-06-16 13:48:42 -0700419 }
420 }
421 }
422
Adam Lesinski082614c2016-03-04 14:33:47 -0800423 if (key.mLibDirs != null) {
424 for (final String libDir : key.mLibDirs) {
Adam Lesinski1dd50c52015-03-16 15:10:56 -0700425 if (libDir.endsWith(".apk")) {
426 // Avoid opening files we know do not have resources,
427 // like code-only .jar files.
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800428 try {
429 builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
430 false /*overlay*/));
431 } catch (IOException e) {
Adam Lesinski1dd50c52015-03-16 15:10:56 -0700432 Log.w(TAG, "Asset path '" + libDir +
433 "' does not exist or contains no resources.");
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800434
435 // continue.
Adam Lesinski1dd50c52015-03-16 15:10:56 -0700436 }
Adam Lesinskide898ff2014-01-29 18:20:45 -0800437 }
438 }
439 }
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800440
Ryan Mitchellee4a5642019-10-16 08:32:55 -0700441 if (key.mOverlayDirs != null) {
442 for (final String idmapPath : key.mOverlayDirs) {
443 try {
444 builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
445 true /*overlay*/));
446 } catch (IOException e) {
447 Log.w(TAG, "failed to add overlay path " + idmapPath);
448
449 // continue.
450 }
451 }
452 }
453
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800454 if (key.mLoaders != null) {
455 for (final ResourcesLoader loader : key.mLoaders) {
456 builder.addLoader(loader);
457 }
458 }
459
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800460 return builder.build();
461 }
462
463 private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
464 int count = 0;
465 for (WeakReference<T> ref : collection) {
466 final T value = ref != null ? ref.get() : null;
467 if (value != null) {
468 count++;
469 }
470 }
471 return count;
472 }
473
474 /**
475 * @hide
476 */
477 public void dump(String prefix, PrintWriter printWriter) {
478 synchronized (this) {
479 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
480 for (int i = 0; i < prefix.length() / 2; i++) {
481 pw.increaseIndent();
482 }
483
484 pw.println("ResourcesManager:");
485 pw.increaseIndent();
Narayan Kamathbc956212018-08-13 12:53:38 +0100486 if (mLoadedApkAssets != null) {
487 pw.print("cached apks: total=");
488 pw.print(mLoadedApkAssets.size());
489 pw.print(" created=");
490 pw.print(mLoadedApkAssets.createCount());
491 pw.print(" evicted=");
492 pw.print(mLoadedApkAssets.evictionCount());
493 pw.print(" hit=");
494 pw.print(mLoadedApkAssets.hitCount());
495 pw.print(" miss=");
496 pw.print(mLoadedApkAssets.missCount());
497 pw.print(" max=");
498 pw.print(mLoadedApkAssets.maxSize());
499 } else {
500 pw.print("cached apks: 0 [cache disabled]");
501 }
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800502 pw.println();
503
504 pw.print("total apks: ");
505 pw.println(countLiveReferences(mCachedApkAssets.values()));
506
507 pw.print("resources: ");
508
509 int references = countLiveReferences(mResourceReferences);
510 for (ActivityResources activityResources : mActivityResourceReferences.values()) {
511 references += countLiveReferences(activityResources.activityResources);
512 }
513 pw.println(references);
514
515 pw.print("resource impls: ");
516 pw.println(countLiveReferences(mResourceImpls.values()));
517 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800518 }
Adam Lesinskide898ff2014-01-29 18:20:45 -0800519
Adam Lesinski082614c2016-03-04 14:33:47 -0800520 private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
Craig Mautner88c05892013-06-28 09:47:45 -0700521 Configuration config;
Adam Lesinski082614c2016-03-04 14:33:47 -0800522 final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
Craig Mautner88c05892013-06-28 09:47:45 -0700523 final boolean hasOverrideConfig = key.hasOverrideConfiguration();
524 if (!isDefaultDisplay || hasOverrideConfig) {
525 config = new Configuration(getConfiguration());
526 if (!isDefaultDisplay) {
Adam Lesinski082614c2016-03-04 14:33:47 -0800527 applyNonDefaultDisplayMetricsToConfiguration(dm, config);
Craig Mautner88c05892013-06-28 09:47:45 -0700528 }
529 if (hasOverrideConfig) {
530 config.updateFrom(key.mOverrideConfiguration);
Wale Ogunwale60454db2015-01-23 16:05:07 -0800531 if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
Craig Mautner88c05892013-06-28 09:47:45 -0700532 }
533 } else {
534 config = getConfiguration();
535 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800536 return config;
537 }
Craig Mautner88c05892013-06-28 09:47:45 -0700538
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700539 private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700540 final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
541 daj.setCompatibilityInfo(key.mCompatInfo);
542
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700543 final AssetManager assets = createAssetManager(key);
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700544 if (assets == null) {
545 return null;
546 }
547
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700548 final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
Adam Lesinski4ece3d62016-06-16 18:05:41 -0700549 final Configuration config = generateConfig(key, dm);
Adam Lesinski8e8d2322016-06-24 12:29:16 -0700550 final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
Bryce Lee609bf652017-02-09 16:50:13 -0800551
Adam Lesinski082614c2016-03-04 14:33:47 -0800552 if (DEBUG) {
553 Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
554 }
555 return impl;
556 }
557
558 /**
559 * Finds a cached ResourcesImpl object that matches the given ResourcesKey.
560 *
561 * @param key The key to match.
562 * @return a ResourcesImpl if the key matches a cache entry, null otherwise.
563 */
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700564 private @Nullable ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
Adam Lesinski082614c2016-03-04 14:33:47 -0800565 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
566 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
567 if (impl != null && impl.getAssets().isUpToDate()) {
568 return impl;
569 }
570 return null;
571 }
572
573 /**
Adam Lesinski25f48882016-06-14 11:05:23 -0700574 * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or
575 * creates a new one and caches it for future use.
576 * @param key The key to match.
577 * @return a ResourcesImpl object matching the key.
578 */
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700579 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
Adam Lesinski25f48882016-06-14 11:05:23 -0700580 @NonNull ResourcesKey key) {
581 ResourcesImpl impl = findResourcesImplForKeyLocked(key);
582 if (impl == null) {
583 impl = createResourcesImpl(key);
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700584 if (impl != null) {
585 mResourceImpls.put(key, new WeakReference<>(impl));
586 }
Adam Lesinski25f48882016-06-14 11:05:23 -0700587 }
588 return impl;
589 }
590
591 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800592 * Find the ResourcesKey that this ResourcesImpl object is associated with.
593 * @return the ResourcesKey or null if none was found.
594 */
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700595 private @Nullable ResourcesKey findKeyForResourceImplLocked(
596 @NonNull ResourcesImpl resourceImpl) {
Winson9947f1e2019-08-16 10:20:39 -0700597 int refCount = mResourceImpls.size();
Adam Lesinski082614c2016-03-04 14:33:47 -0800598 for (int i = 0; i < refCount; i++) {
599 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
600 ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
Winson9947f1e2019-08-16 10:20:39 -0700601 if (resourceImpl == impl) {
Adam Lesinski082614c2016-03-04 14:33:47 -0800602 return mResourceImpls.keyAt(i);
603 }
604 }
605 return null;
606 }
607
Andrii Kulian3b3c9142016-07-18 19:15:56 -0700608 /**
609 * Check if activity resources have same override config as the provided on.
610 * @param activityToken The Activity that resources should be associated with.
611 * @param overrideConfig The override configuration to be checked for equality with.
612 * @return true if activity resources override config matches the provided one or they are both
613 * null, false otherwise.
614 */
615 boolean isSameResourcesOverrideConfig(@Nullable IBinder activityToken,
616 @Nullable Configuration overrideConfig) {
617 synchronized (this) {
618 final ActivityResources activityResources
619 = activityToken != null ? mActivityResourceReferences.get(activityToken) : null;
620 if (activityResources == null) {
621 return overrideConfig == null;
622 } else {
Bryce Lee658d9842017-07-28 08:33:36 -0700623 // The two configurations must either be equal or publicly equivalent to be
624 // considered the same.
625 return Objects.equals(activityResources.overrideConfig, overrideConfig)
626 || (overrideConfig != null && activityResources.overrideConfig != null
627 && 0 == overrideConfig.diffPublicOnly(
628 activityResources.overrideConfig));
Andrii Kulian3b3c9142016-07-18 19:15:56 -0700629 }
630 }
631 }
632
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700633 private ActivityResources getOrCreateActivityResourcesStructLocked(
634 @NonNull IBinder activityToken) {
635 ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
636 if (activityResources == null) {
637 activityResources = new ActivityResources();
638 mActivityResourceReferences.put(activityToken, activityResources);
639 }
640 return activityResources;
641 }
642
Winson9947f1e2019-08-16 10:20:39 -0700643 @Nullable
644 private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
645 @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
Winson9947f1e2019-08-16 10:20:39 -0700646 ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
647 targetActivityToken);
648
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800649 final int size = activityResources.activityResources.size();
Winson9947f1e2019-08-16 10:20:39 -0700650 for (int index = 0; index < size; index++) {
651 WeakReference<Resources> ref = activityResources.activityResources.get(index);
652 Resources resources = ref.get();
653 ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
654 resources.getImpl());
655
656 if (key != null
657 && Objects.equals(resources.getClassLoader(), targetClassLoader)
658 && Objects.equals(key, targetKey)) {
659 return resources;
660 }
661 }
662
663 return null;
664 }
665
666 private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
Jason Monkbd60e5b2017-03-02 12:55:00 -0500667 @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
668 @NonNull CompatibilityInfo compatInfo) {
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700669 final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
670 activityToken);
671
Jason Monkbd60e5b2017-03-02 12:55:00 -0500672 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
673 : new Resources(classLoader);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700674 resources.setImpl(impl);
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800675 resources.setCallbacks(mUpdateCallbacks);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700676 activityResources.activityResources.add(new WeakReference<>(resources));
677 if (DEBUG) {
678 Slog.d(TAG, "- creating new ref=" + resources);
679 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
Adam Lesinski082614c2016-03-04 14:33:47 -0800680 }
681 return resources;
682 }
683
Winson9947f1e2019-08-16 10:20:39 -0700684 private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
Jason Monkbd60e5b2017-03-02 12:55:00 -0500685 @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
Jason Monkbd60e5b2017-03-02 12:55:00 -0500686 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
687 : new Resources(classLoader);
Adam Lesinski082614c2016-03-04 14:33:47 -0800688 resources.setImpl(impl);
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800689 resources.setCallbacks(mUpdateCallbacks);
Adam Lesinski082614c2016-03-04 14:33:47 -0800690 mResourceReferences.add(new WeakReference<>(resources));
691 if (DEBUG) {
692 Slog.d(TAG, "- creating new ref=" + resources);
693 Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
694 }
695 return resources;
696 }
697
698 /**
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700699 * Creates base resources for an Activity. Calls to
700 * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800701 * CompatibilityInfo, ClassLoader, List)} with the same activityToken will have their override
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700702 * configurations merged with the one specified here.
703 *
704 * @param activityToken Represents an Activity.
705 * @param resDir The base resource path. Can be null (only framework resources will be loaded).
706 * @param splitResDirs An array of split resource paths. Can be null.
707 * @param overlayDirs An array of overlay paths. Can be null.
708 * @param libDirs An array of resource library paths. Can be null.
709 * @param displayId The ID of the display for which to create the resources.
710 * @param overrideConfig The configuration to apply on top of the base configuration. Can be
711 * null. This provides the base override for this Activity.
712 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
713 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
714 * @param classLoader The class loader to use when inflating Resources. If null, the
715 * {@link ClassLoader#getSystemClassLoader()} is used.
716 * @return a Resources object from which to access resources.
717 */
Adam Lesinski53fafdf2016-08-03 13:36:39 -0700718 public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700719 @Nullable String resDir,
720 @Nullable String[] splitResDirs,
721 @Nullable String[] overlayDirs,
722 @Nullable String[] libDirs,
723 int displayId,
724 @Nullable Configuration overrideConfig,
725 @NonNull CompatibilityInfo compatInfo,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800726 @Nullable ClassLoader classLoader,
727 @Nullable List<ResourcesLoader> loaders) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700728 try {
729 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
730 "ResourcesManager#createBaseActivityResources");
731 final ResourcesKey key = new ResourcesKey(
732 resDir,
733 splitResDirs,
734 overlayDirs,
735 libDirs,
736 displayId,
737 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800738 compatInfo,
739 loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700740 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700741
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700742 if (DEBUG) {
743 Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
744 + " with key=" + key);
Tim Murray98e80072016-04-14 15:44:35 -0700745 }
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700746
747 synchronized (this) {
Adam Lesinski8ce4e122016-05-02 16:09:16 -0700748 // Force the creation of an ActivityResourcesStruct.
749 getOrCreateActivityResourcesStructLocked(activityToken);
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700750 }
751
752 // Update any existing Activity Resources references.
Andrii Kulianb047b8b2017-02-08 18:38:26 -0800753 updateResourcesForActivity(activityToken, overrideConfig, displayId,
754 false /* movedToDifferentDisplay */);
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700755
Winson9947f1e2019-08-16 10:20:39 -0700756 cleanupReferences(activityToken);
757 rebaseKeyForActivity(activityToken, key);
758
759 synchronized (this) {
760 Resources resources = findResourcesForActivityLocked(activityToken, key,
761 classLoader);
762 if (resources != null) {
763 return resources;
764 }
765 }
766
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700767 // Now request an actual Resources object.
Winson9947f1e2019-08-16 10:20:39 -0700768 return createResources(activityToken, key, classLoader);
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700769 } finally {
770 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Tim Murray98e80072016-04-14 15:44:35 -0700771 }
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700772 }
773
774 /**
Winson9947f1e2019-08-16 10:20:39 -0700775 * Rebases a key's override config on top of the Activity's base override.
776 */
777 private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
778 final ActivityResources activityResources =
779 getOrCreateActivityResourcesStructLocked(activityToken);
780
781 // Clean up any dead references so they don't pile up.
782 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
783 sEmptyReferencePredicate);
784
785 // Rebase the key's override config on top of the Activity's base override.
786 if (key.hasOverrideConfiguration()
787 && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
788 final Configuration temp = new Configuration(activityResources.overrideConfig);
789 temp.updateFrom(key.mOverrideConfiguration);
790 key.mOverrideConfiguration.setTo(temp);
791 }
792 }
793
794 /**
795 * Check WeakReferences and remove any dead references so they don't pile up.
796 * @param activityToken optional token to clean up Activity resources
797 */
798 private void cleanupReferences(IBinder activityToken) {
Winson02c7c4c2019-10-14 11:26:42 -0700799 synchronized (this) {
800 if (activityToken != null) {
801 ActivityResources activityResources = mActivityResourceReferences.get(
802 activityToken);
803 if (activityResources != null) {
804 ArrayUtils.unstableRemoveIf(activityResources.activityResources,
805 sEmptyReferencePredicate);
806 }
807 } else {
808 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
Winson9947f1e2019-08-16 10:20:39 -0700809 }
Winson9947f1e2019-08-16 10:20:39 -0700810 }
811 }
812
813 /**
814 * Creates a Resources object set with a ResourcesImpl object matching the given key.
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700815 *
816 * @param activityToken The Activity this Resources object should be associated with.
817 * @param key The key describing the parameters of the ResourcesImpl object.
818 * @param classLoader The classloader to use for the Resources object.
819 * If null, {@link ClassLoader#getSystemClassLoader()} is used.
820 * @return A Resources object that gets updated when
821 * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
822 * is called.
823 */
Winson9947f1e2019-08-16 10:20:39 -0700824 private @Nullable Resources createResources(@Nullable IBinder activityToken,
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700825 @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700826 synchronized (this) {
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700827 if (DEBUG) {
828 Throwable here = new Throwable();
829 here.fillInStackTrace();
830 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
831 }
832
Winson9947f1e2019-08-16 10:20:39 -0700833 ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
Adam Lesinskibebfcc42018-02-12 14:27:46 -0800834 if (resourcesImpl == null) {
835 return null;
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700836 }
837
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700838 if (activityToken != null) {
Winson9947f1e2019-08-16 10:20:39 -0700839 return createResourcesForActivityLocked(activityToken, classLoader,
Jason Monkbd60e5b2017-03-02 12:55:00 -0500840 resourcesImpl, key.mCompatInfo);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700841 } else {
Winson9947f1e2019-08-16 10:20:39 -0700842 return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700843 }
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700844 }
845 }
846
847 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800848 * Gets or creates a new Resources object associated with the IBinder token. References returned
849 * by this method live as long as the Activity, meaning they can be cached and used by the
850 * Activity even after a configuration change. If any other parameter is changed
851 * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
852 * is updated and handed back to the caller. However, changing the class loader will result in a
853 * new Resources object.
854 * <p/>
855 * If activityToken is null, a cached Resources object will be returned if it matches the
856 * input parameters. Otherwise a new Resources object that satisfies these parameters is
857 * returned.
858 *
859 * @param activityToken Represents an Activity. If null, global resources are assumed.
860 * @param resDir The base resource path. Can be null (only framework resources will be loaded).
861 * @param splitResDirs An array of split resource paths. Can be null.
862 * @param overlayDirs An array of overlay paths. Can be null.
863 * @param libDirs An array of resource library paths. Can be null.
864 * @param displayId The ID of the display for which to create the resources.
865 * @param overrideConfig The configuration to apply on top of the base configuration. Can be
866 * null. Mostly used with Activities that are in multi-window which may override width and
867 * height properties from the base config.
868 * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
869 * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
870 * @param classLoader The class loader to use when inflating Resources. If null, the
871 * {@link ClassLoader#getSystemClassLoader()} is used.
872 * @return a Resources object from which to access resources.
873 */
Winson9947f1e2019-08-16 10:20:39 -0700874 public @Nullable Resources getResources(
875 @Nullable IBinder activityToken,
Adam Lesinski082614c2016-03-04 14:33:47 -0800876 @Nullable String resDir,
877 @Nullable String[] splitResDirs,
878 @Nullable String[] overlayDirs,
879 @Nullable String[] libDirs,
880 int displayId,
881 @Nullable Configuration overrideConfig,
882 @NonNull CompatibilityInfo compatInfo,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800883 @Nullable ClassLoader classLoader,
884 @Nullable List<ResourcesLoader> loaders) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700885 try {
886 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
887 final ResourcesKey key = new ResourcesKey(
888 resDir,
889 splitResDirs,
890 overlayDirs,
891 libDirs,
892 displayId,
893 overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800894 compatInfo,
895 loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700896 classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
Winson9947f1e2019-08-16 10:20:39 -0700897
898 cleanupReferences(activityToken);
899
900 if (activityToken != null) {
901 rebaseKeyForActivity(activityToken, key);
902 }
903
904 return createResources(activityToken, key, classLoader);
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700905 } finally {
906 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
907 }
Craig Mautner88c05892013-06-28 09:47:45 -0700908 }
909
Todd Kennedy39bfee52016-02-24 10:28:21 -0800910 /**
Adam Lesinski082614c2016-03-04 14:33:47 -0800911 * Updates an Activity's Resources object with overrideConfig. The Resources object
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800912 * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
913 * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will
914 * have the updated configuration.
915 *
Adam Lesinski082614c2016-03-04 14:33:47 -0800916 * @param activityToken The Activity token.
917 * @param overrideConfig The configuration override to update.
Andrii Kulianb047b8b2017-02-08 18:38:26 -0800918 * @param displayId Id of the display where activity currently resides.
919 * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
Todd Kennedy39bfee52016-02-24 10:28:21 -0800920 */
Adam Lesinski082614c2016-03-04 14:33:47 -0800921 public void updateResourcesForActivity(@NonNull IBinder activityToken,
Andrii Kulianb047b8b2017-02-08 18:38:26 -0800922 @Nullable Configuration overrideConfig, int displayId,
923 boolean movedToDifferentDisplay) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700924 try {
925 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
926 "ResourcesManager#updateResourcesForActivity");
927 synchronized (this) {
928 final ActivityResources activityResources =
929 getOrCreateActivityResourcesStructLocked(activityToken);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700930
Andrii Kulianb047b8b2017-02-08 18:38:26 -0800931 if (Objects.equals(activityResources.overrideConfig, overrideConfig)
932 && !movedToDifferentDisplay) {
933 // They are the same and no change of display id, no work to do.
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700934 return;
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700935 }
936
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700937 // Grab a copy of the old configuration so we can create the delta's of each
938 // Resources object associated with this Activity.
939 final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700940
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700941 // Update the Activity's base override.
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700942 if (overrideConfig != null) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700943 activityResources.overrideConfig.setTo(overrideConfig);
944 } else {
Adam Lesinskibad43fc2016-07-19 13:35:01 -0700945 activityResources.overrideConfig.unset();
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700946 }
947
Adam Lesinski0cd9a7ea2016-04-01 11:48:10 -0700948 if (DEBUG) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700949 Throwable here = new Throwable();
950 here.fillInStackTrace();
951 Slog.d(TAG, "updating resources override for activity=" + activityToken
952 + " from oldConfig="
953 + Configuration.resourceQualifierString(oldConfig)
954 + " to newConfig="
955 + Configuration.resourceQualifierString(
Andrii Kulianb047b8b2017-02-08 18:38:26 -0800956 activityResources.overrideConfig) + " displayId=" + displayId,
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700957 here);
Adam Lesinski0cd9a7ea2016-04-01 11:48:10 -0700958 }
959
Adam Lesinski7f3f4992016-03-30 10:32:15 -0700960
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700961 // Rebase each Resources associated with this Activity.
962 final int refCount = activityResources.activityResources.size();
963 for (int i = 0; i < refCount; i++) {
964 WeakReference<Resources> weakResRef = activityResources.activityResources.get(
965 i);
Winson9947f1e2019-08-16 10:20:39 -0700966
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700967 Resources resources = weakResRef.get();
968 if (resources == null) {
969 continue;
970 }
971
Winson9947f1e2019-08-16 10:20:39 -0700972 ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
973 overrideConfig, displayId);
974 updateActivityResources(resources, newKey, false);
975 }
Adam Lesinski082614c2016-03-04 14:33:47 -0800976 }
Adam Lesinskib7e1ce02016-04-11 20:03:01 -0700977 } finally {
978 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
Adam Lesinski082614c2016-03-04 14:33:47 -0800979 }
Todd Kennedy39bfee52016-02-24 10:28:21 -0800980 }
981
Winson9947f1e2019-08-16 10:20:39 -0700982 /**
983 * Rebases an updated override config over any old override config and returns the new one
984 * that an Activity's Resources should be set to.
985 */
986 private ResourcesKey rebaseActivityOverrideConfig(Resources resources,
987 Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
988 int displayId) {
989 // Extract the ResourcesKey that was last used to create the Resources for this
990 // activity.
991 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
992 if (oldKey == null) {
993 Slog.e(TAG, "can't find ResourcesKey for resources impl="
994 + resources.getImpl());
995 return null;
996 }
997
998 // Build the new override configuration for this ResourcesKey.
999 final Configuration rebasedOverrideConfig = new Configuration();
1000 if (newOverrideConfig != null) {
1001 rebasedOverrideConfig.setTo(newOverrideConfig);
1002 }
1003
1004 final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
1005 if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
1006 // Generate a delta between the old base Activity override configuration and
1007 // the actual final override configuration that was used to figure out the
1008 // real delta this Resources object wanted.
1009 Configuration overrideOverrideConfig = Configuration.generateDelta(
1010 oldOverrideConfig, oldKey.mOverrideConfiguration);
1011 rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
1012 }
1013
1014 // Create the new ResourcesKey with the rebased override config.
1015 final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001016 oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs,
1017 displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
Winson9947f1e2019-08-16 10:20:39 -07001018
1019 if (DEBUG) {
1020 Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
1021 + " to newKey=" + newKey + ", displayId=" + displayId);
1022 }
1023
1024 return newKey;
1025 }
1026
1027 private void updateActivityResources(Resources resources, ResourcesKey newKey,
1028 boolean hasLoader) {
1029 final ResourcesImpl resourcesImpl;
1030
1031 if (hasLoader) {
1032 // Loaders always get new Impls because they cannot be shared
1033 resourcesImpl = createResourcesImpl(newKey);
1034 } else {
1035 resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
1036 }
1037
1038 if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
1039 // Set the ResourcesImpl, updating it for all users of this Resources
1040 // object.
1041 resources.setImpl(resourcesImpl);
1042 }
1043 }
1044
Ryan Mitchell4043ca72019-06-03 16:11:24 -07001045 @TestApi
1046 public final boolean applyConfigurationToResources(@NonNull Configuration config,
1047 @Nullable CompatibilityInfo compat) {
1048 synchronized(this) {
1049 return applyConfigurationToResourcesLocked(config, compat);
1050 }
1051 }
1052
Adam Lesinski082614c2016-03-04 14:33:47 -08001053 public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
1054 @Nullable CompatibilityInfo compat) {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001055 try {
1056 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1057 "ResourcesManager#applyConfigurationToResourcesLocked");
Tim Murray98e80072016-04-14 15:44:35 -07001058
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001059 if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
1060 if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
1061 + mResConfiguration.seq + ", newSeq=" + config.seq);
1062 return false;
1063 }
1064 int changes = mResConfiguration.updateFrom(config);
1065 // Things might have changed in display manager, so clear the cached displays.
Bryce Lee609bf652017-02-09 16:50:13 -08001066 mAdjustedDisplays.clear();
Bryce Lee609bf652017-02-09 16:50:13 -08001067
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001068 DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
Tim Murray98e80072016-04-14 15:44:35 -07001069
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001070 if (compat != null && (mResCompatibilityInfo == null ||
1071 !mResCompatibilityInfo.equals(compat))) {
1072 mResCompatibilityInfo = compat;
1073 changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT
1074 | ActivityInfo.CONFIG_SCREEN_SIZE
1075 | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
1076 }
1077
Adam Lesinskib61e4052016-05-19 18:23:05 -07001078 Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
Tim Murray98e80072016-04-14 15:44:35 -07001079
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001080 ApplicationPackageManager.configurationChanged();
1081 //Slog.i(TAG, "Configuration changed in " + currentPackageName());
Tim Murray98e80072016-04-14 15:44:35 -07001082
Winson9947f1e2019-08-16 10:20:39 -07001083 Configuration tmpConfig = new Configuration();
Tim Murray98e80072016-04-14 15:44:35 -07001084
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001085 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1086 ResourcesKey key = mResourceImpls.keyAt(i);
liangweikanga9c9bf52016-11-03 16:12:08 +08001087 WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1088 ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001089 if (r != null) {
Andrii Kulian946e8df2019-12-12 12:15:33 -08001090 applyConfigurationToResourcesLocked(config, compat, tmpConfig, key, r);
Tim Murray98e80072016-04-14 15:44:35 -07001091 } else {
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001092 mResourceImpls.removeAt(i);
Tim Murray98e80072016-04-14 15:44:35 -07001093 }
Tim Murray98e80072016-04-14 15:44:35 -07001094 }
Tim Murray98e80072016-04-14 15:44:35 -07001095
Adam Lesinskib7e1ce02016-04-11 20:03:01 -07001096 return changes != 0;
1097 } finally {
1098 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1099 }
Craig Mautner88c05892013-06-28 09:47:45 -07001100 }
Adam Lesinski25f48882016-06-14 11:05:23 -07001101
Winson9947f1e2019-08-16 10:20:39 -07001102 private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
1103 @Nullable CompatibilityInfo compat, Configuration tmpConfig,
Andrii Kulian946e8df2019-12-12 12:15:33 -08001104 ResourcesKey key, ResourcesImpl resourcesImpl) {
Winson9947f1e2019-08-16 10:20:39 -07001105 if (DEBUG || DEBUG_CONFIGURATION) {
1106 Slog.v(TAG, "Changing resources "
1107 + resourcesImpl + " config to: " + config);
1108 }
1109 int displayId = key.mDisplayId;
Winson9947f1e2019-08-16 10:20:39 -07001110 final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
Andrii Kulian946e8df2019-12-12 12:15:33 -08001111 tmpConfig.setTo(config);
Winson9947f1e2019-08-16 10:20:39 -07001112
Andrii Kulian946e8df2019-12-12 12:15:33 -08001113 // Get new DisplayMetrics based on the DisplayAdjustments given to the ResourcesImpl. Update
1114 // a copy if the CompatibilityInfo changed, because the ResourcesImpl object will handle the
1115 // update internally.
1116 DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
1117 if (compat != null) {
1118 daj = new DisplayAdjustments(daj);
1119 daj.setCompatibilityInfo(compat);
Winson9947f1e2019-08-16 10:20:39 -07001120 }
Andrii Kulian946e8df2019-12-12 12:15:33 -08001121 daj.setConfiguration(config);
1122 DisplayMetrics dm = getDisplayMetrics(displayId, daj);
1123 if (displayId != Display.DEFAULT_DISPLAY) {
1124 applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
1125 }
1126
1127 if (hasOverrideConfiguration) {
1128 tmpConfig.updateFrom(key.mOverrideConfiguration);
1129 }
1130 resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
Winson9947f1e2019-08-16 10:20:39 -07001131 }
1132
Adam Lesinski25f48882016-06-14 11:05:23 -07001133 /**
1134 * Appends the library asset path to any ResourcesImpl object that contains the main
1135 * assetPath.
1136 * @param assetPath The main asset path for which to add the library asset path.
1137 * @param libAsset The library asset path to add.
1138 */
Mathew Inwood61e8ae62018-08-14 14:17:44 +01001139 @UnsupportedAppUsage
Adam Lesinski25f48882016-06-14 11:05:23 -07001140 public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) {
Torne (Richard Coles)527fa8f2019-02-08 15:11:02 -05001141 appendLibAssetsForMainAssetPath(assetPath, new String[] { libAsset });
1142 }
1143
1144 /**
1145 * Appends the library asset paths to any ResourcesImpl object that contains the main
1146 * assetPath.
1147 * @param assetPath The main asset path for which to add the library asset path.
1148 * @param libAssets The library asset paths to add.
1149 */
1150 public void appendLibAssetsForMainAssetPath(String assetPath, String[] libAssets) {
Adam Lesinski25f48882016-06-14 11:05:23 -07001151 synchronized (this) {
1152 // Record which ResourcesImpl need updating
1153 // (and what ResourcesKey they should update to).
1154 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1155
1156 final int implCount = mResourceImpls.size();
1157 for (int i = 0; i < implCount; i++) {
Adam Lesinski25f48882016-06-14 11:05:23 -07001158 final ResourcesKey key = mResourceImpls.keyAt(i);
liangweikanga9c9bf52016-11-03 16:12:08 +08001159 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1160 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
Adam Lesinskicd7197d2017-04-05 11:04:17 -07001161 if (impl != null && Objects.equals(key.mResDir, assetPath)) {
Torne (Richard Coles)527fa8f2019-02-08 15:11:02 -05001162 String[] newLibAssets = key.mLibDirs;
1163 for (String libAsset : libAssets) {
1164 newLibAssets =
1165 ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
1166 }
Adam Lesinski25f48882016-06-14 11:05:23 -07001167
Winson9947f1e2019-08-16 10:20:39 -07001168 if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
Adam Lesinski25f48882016-06-14 11:05:23 -07001169 updatedResourceKeys.put(impl, new ResourcesKey(
1170 key.mResDir,
1171 key.mSplitResDirs,
1172 key.mOverlayDirs,
1173 newLibAssets,
1174 key.mDisplayId,
1175 key.mOverrideConfiguration,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001176 key.mCompatInfo,
1177 key.mLoaders));
Winson9947f1e2019-08-16 10:20:39 -07001178 }
1179 }
1180 }
1181
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001182 redirectResourcesToNewImplLocked(updatedResourceKeys);
1183 }
1184 }
1185
1186 // TODO(adamlesinski): Make this accept more than just overlay directories.
Winsond605e2d2019-02-11 16:05:51 -08001187 final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
1188 @Nullable final String[] oldPaths) {
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001189 try {
1190 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
1191 "ResourcesManager#applyNewResourceDirsLocked");
1192
Winsond605e2d2019-02-11 16:05:51 -08001193 String baseCodePath = appInfo.getBaseCodePath();
1194
1195 final int myUid = Process.myUid();
1196 String[] newSplitDirs = appInfo.uid == myUid
1197 ? appInfo.splitSourceDirs
1198 : appInfo.splitPublicSourceDirs;
1199
1200 // ApplicationInfo is mutable, so clone the arrays to prevent outside modification
1201 String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs);
1202 String[] copiedResourceDirs = ArrayUtils.cloneOrNull(appInfo.resourceDirs);
1203
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001204 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
1205 final int implCount = mResourceImpls.size();
1206 for (int i = 0; i < implCount; i++) {
1207 final ResourcesKey key = mResourceImpls.keyAt(i);
1208 final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
1209 final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
Winsond605e2d2019-02-11 16:05:51 -08001210
1211 if (impl == null) {
1212 continue;
1213 }
1214
1215 if (key.mResDir == null
1216 || key.mResDir.equals(baseCodePath)
1217 || ArrayUtils.contains(oldPaths, key.mResDir)) {
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001218 updatedResourceKeys.put(impl, new ResourcesKey(
Winsond605e2d2019-02-11 16:05:51 -08001219 baseCodePath,
1220 copiedSplitDirs,
1221 copiedResourceDirs,
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001222 key.mLibDirs,
1223 key.mDisplayId,
1224 key.mOverrideConfiguration,
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001225 key.mCompatInfo,
1226 key.mLoaders
Winsond605e2d2019-02-11 16:05:51 -08001227 ));
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001228 }
Adam Lesinski25f48882016-06-14 11:05:23 -07001229 }
1230
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001231 redirectResourcesToNewImplLocked(updatedResourceKeys);
1232 } finally {
1233 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
1234 }
1235 }
1236
1237 private void redirectResourcesToNewImplLocked(
1238 @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) {
1239 // Bail early if there is no work to do.
1240 if (updatedResourceKeys.isEmpty()) {
1241 return;
1242 }
1243
1244 // Update any references to ResourcesImpl that require reloading.
1245 final int resourcesCount = mResourceReferences.size();
1246 for (int i = 0; i < resourcesCount; i++) {
1247 final WeakReference<Resources> ref = mResourceReferences.get(i);
1248 final Resources r = ref != null ? ref.get() : null;
1249 if (r != null) {
1250 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1251 if (key != null) {
1252 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1253 if (impl == null) {
1254 throw new Resources.NotFoundException("failed to redirect ResourcesImpl");
1255 }
1256 r.setImpl(impl);
1257 }
1258 }
1259 }
1260
1261 // Update any references to ResourcesImpl that require reloading for each Activity.
1262 for (ActivityResources activityResources : mActivityResourceReferences.values()) {
1263 final int resCount = activityResources.activityResources.size();
1264 for (int i = 0; i < resCount; i++) {
1265 final WeakReference<Resources> ref = activityResources.activityResources.get(i);
1266 final Resources r = ref != null ? ref.get() : null;
Adam Lesinski25f48882016-06-14 11:05:23 -07001267 if (r != null) {
1268 final ResourcesKey key = updatedResourceKeys.get(r.getImpl());
1269 if (key != null) {
Adam Lesinski53fafdf2016-08-03 13:36:39 -07001270 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key);
1271 if (impl == null) {
Mårten Kongstad49a4a1d2017-01-12 08:36:37 +01001272 throw new Resources.NotFoundException(
1273 "failed to redirect ResourcesImpl");
Adam Lesinski53fafdf2016-08-03 13:36:39 -07001274 }
1275 r.setImpl(impl);
Adam Lesinski25f48882016-06-14 11:05:23 -07001276 }
1277 }
1278 }
Adam Lesinski25f48882016-06-14 11:05:23 -07001279 }
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001280 }
Winson9947f1e2019-08-16 10:20:39 -07001281
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001282 private class UpdateHandler implements Resources.UpdateCallbacks {
1283
1284 /**
1285 * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
1286 * instance uses.
1287 */
1288 @Override
1289 public void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoader) {
1290 synchronized (ResourcesManager.this) {
1291 final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
1292 if (oldKey == null) {
1293 throw new IllegalArgumentException("Cannot modify resource loaders of"
1294 + " ResourcesImpl not registered with ResourcesManager");
1295 }
1296
1297 final ResourcesKey newKey = new ResourcesKey(
1298 oldKey.mResDir,
1299 oldKey.mSplitResDirs,
1300 oldKey.mOverlayDirs,
1301 oldKey.mLibDirs,
1302 oldKey.mDisplayId,
1303 oldKey.mOverrideConfiguration,
1304 oldKey.mCompatInfo,
1305 newLoader.toArray(new ResourcesLoader[0]));
1306
1307 final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
1308 resources.setImpl(impl);
Winson9947f1e2019-08-16 10:20:39 -07001309 }
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001310 }
Winson9947f1e2019-08-16 10:20:39 -07001311
Ryan Mitchell4579c0a2020-01-08 16:29:11 -08001312 /**
1313 * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
1314 * {@code loader} to apply any changes of the set of {@link ApkAssets}.
1315 **/
1316 @Override
1317 public void onLoaderUpdated(ResourcesLoader loader) {
1318 synchronized (ResourcesManager.this) {
1319 final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
1320 new ArrayMap<>();
1321
1322 for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
1323 final ResourcesKey key = mResourceImpls.keyAt(i);
1324 final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
1325 if (impl == null || impl.get() == null
1326 || !ArrayUtils.contains(key.mLoaders, loader)) {
1327 continue;
1328 }
1329
1330 mResourceImpls.remove(key);
1331 updatedResourceImplKeys.put(impl.get(), key);
1332 }
1333
1334 redirectResourcesToNewImplLocked(updatedResourceImplKeys);
Winson9947f1e2019-08-16 10:20:39 -07001335 }
Winson9947f1e2019-08-16 10:20:39 -07001336 }
Adam Lesinski25f48882016-06-14 11:05:23 -07001337 }
Seigo Nonakac14dd782016-03-30 23:09:16 +09001338}