Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 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 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 21 | import android.annotation.NonNull; |
| 22 | import android.annotation.Nullable; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 23 | import android.content.pm.ActivityInfo; |
| 24 | import android.content.res.AssetManager; |
| 25 | import android.content.res.CompatibilityInfo; |
| 26 | import android.content.res.Configuration; |
| 27 | import android.content.res.Resources; |
Adam Lesinski | fb302cc | 2016-02-29 16:50:38 -0800 | [diff] [blame] | 28 | import android.content.res.ResourcesImpl; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 29 | import android.content.res.ResourcesKey; |
| 30 | import android.hardware.display.DisplayManagerGlobal; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 31 | import android.os.IBinder; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 32 | import android.util.ArrayMap; |
| 33 | import android.util.DisplayMetrics; |
Seigo Nonaka | cada57a | 2016-01-22 17:22:11 +0900 | [diff] [blame] | 34 | import android.util.LocaleList; |
Adam Lesinski | 1dd50c5 | 2015-03-16 15:10:56 -0700 | [diff] [blame] | 35 | import android.util.Log; |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 36 | import android.util.Pair; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 37 | import android.util.Slog; |
| 38 | import android.view.Display; |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 39 | import android.view.DisplayAdjustments; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 40 | import com.android.internal.annotations.VisibleForTesting; |
| 41 | import com.android.internal.util.ArrayUtils; |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 42 | |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 43 | import java.lang.ref.WeakReference; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 44 | import java.util.ArrayList; |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 45 | import java.util.Arrays; |
| 46 | import java.util.HashSet; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 47 | import java.util.Objects; |
| 48 | import java.util.WeakHashMap; |
| 49 | import java.util.function.Predicate; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 50 | |
| 51 | /** @hide */ |
| 52 | public class ResourcesManager { |
| 53 | static final String TAG = "ResourcesManager"; |
Wale Ogunwale | 60454db | 2015-01-23 16:05:07 -0800 | [diff] [blame] | 54 | private static final boolean DEBUG = false; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 55 | |
| 56 | private static ResourcesManager sResourcesManager; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 57 | |
| 58 | /** |
| 59 | * Predicate that returns true if a WeakReference is gc'ed. |
| 60 | */ |
| 61 | private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate = |
| 62 | new Predicate<WeakReference<Resources>>() { |
| 63 | @Override |
| 64 | public boolean test(WeakReference<Resources> weakRef) { |
| 65 | return weakRef == null || weakRef.get() == null; |
| 66 | } |
| 67 | }; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 68 | |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 69 | private String[] mSystemLocales = {}; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 70 | private final HashSet<String> mNonSystemLocales = new HashSet<>(); |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 71 | private boolean mHasNonSystemLocales = false; |
| 72 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 73 | /** |
| 74 | * The global compatibility settings. |
| 75 | */ |
| 76 | private CompatibilityInfo mResCompatibilityInfo; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 77 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 78 | /** |
| 79 | * The global configuration upon which all Resources are based. Multi-window Resources |
| 80 | * apply their overrides to this configuration. |
| 81 | */ |
| 82 | private final Configuration mResConfiguration = new Configuration(); |
| 83 | |
| 84 | /** |
| 85 | * A mapping of ResourceImpls and their configurations. These are heavy weight objects |
| 86 | * which should be reused as much as possible. |
| 87 | */ |
| 88 | private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls = |
| 89 | new ArrayMap<>(); |
| 90 | |
| 91 | /** |
| 92 | * A list of Resource references that can be reused. |
| 93 | */ |
| 94 | private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>(); |
| 95 | |
| 96 | /** |
| 97 | * Each Activity may have only one Resources object. |
| 98 | */ |
| 99 | private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences = |
| 100 | new WeakHashMap<>(); |
| 101 | |
| 102 | /** |
| 103 | * A cache of DisplayId to DisplayAdjustments. |
| 104 | */ |
| 105 | private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays = |
| 106 | new ArrayMap<>(); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 107 | |
| 108 | public static ResourcesManager getInstance() { |
| 109 | synchronized (ResourcesManager.class) { |
| 110 | if (sResourcesManager == null) { |
| 111 | sResourcesManager = new ResourcesManager(); |
| 112 | } |
| 113 | return sResourcesManager; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | public Configuration getConfiguration() { |
| 118 | return mResConfiguration; |
| 119 | } |
| 120 | |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 121 | DisplayMetrics getDisplayMetricsLocked() { |
| 122 | return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 123 | } |
| 124 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 125 | /** |
| 126 | * Protected so that tests can override and returns something a fixed value. |
| 127 | */ |
| 128 | @VisibleForTesting |
| 129 | protected DisplayMetrics getDisplayMetricsLocked(int displayId) { |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 130 | DisplayMetrics dm = new DisplayMetrics(); |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 131 | final Display display = |
| 132 | getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 133 | if (display != null) { |
| 134 | display.getMetrics(dm); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 135 | } else { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 136 | dm.setToDefaults(); |
| 137 | } |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 138 | return dm; |
| 139 | } |
| 140 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 141 | private static void applyNonDefaultDisplayMetricsToConfiguration( |
| 142 | @NonNull DisplayMetrics dm, @NonNull Configuration config) { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 143 | config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; |
| 144 | config.densityDpi = dm.densityDpi; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 145 | config.screenWidthDp = (int) (dm.widthPixels / dm.density); |
| 146 | config.screenHeightDp = (int) (dm.heightPixels / dm.density); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 147 | int sl = Configuration.resetScreenLayout(config.screenLayout); |
| 148 | if (dm.widthPixels > dm.heightPixels) { |
| 149 | config.orientation = Configuration.ORIENTATION_LANDSCAPE; |
| 150 | config.screenLayout = Configuration.reduceScreenLayout(sl, |
| 151 | config.screenWidthDp, config.screenHeightDp); |
| 152 | } else { |
| 153 | config.orientation = Configuration.ORIENTATION_PORTRAIT; |
| 154 | config.screenLayout = Configuration.reduceScreenLayout(sl, |
| 155 | config.screenHeightDp, config.screenWidthDp); |
| 156 | } |
| 157 | config.smallestScreenWidthDp = config.screenWidthDp; // assume screen does not rotate |
| 158 | config.compatScreenWidthDp = config.screenWidthDp; |
| 159 | config.compatScreenHeightDp = config.screenHeightDp; |
| 160 | config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; |
| 161 | } |
| 162 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 163 | public boolean applyCompatConfigurationLocked(int displayDensity, |
| 164 | @NonNull Configuration compatConfiguration) { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 165 | if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { |
| 166 | mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); |
| 167 | return true; |
| 168 | } |
| 169 | return false; |
| 170 | } |
| 171 | |
| 172 | /** |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 173 | * Returns an adjusted {@link Display} object based on the inputs or null if display isn't |
| 174 | * available. |
| 175 | * |
| 176 | * @param displayId display Id. |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 177 | * @param displayAdjustments display adjustments. |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 178 | */ |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 179 | public Display getAdjustedDisplay(final int displayId, |
| 180 | @Nullable DisplayAdjustments displayAdjustments) { |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 181 | final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) |
| 182 | ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); |
| 183 | final Pair<Integer, DisplayAdjustments> key = |
| 184 | Pair.create(displayId, displayAdjustmentsCopy); |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 185 | synchronized (this) { |
| 186 | WeakReference<Display> wd = mDisplays.get(key); |
| 187 | if (wd != null) { |
| 188 | final Display display = wd.get(); |
| 189 | if (display != null) { |
| 190 | return display; |
| 191 | } |
| 192 | } |
| 193 | final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); |
| 194 | if (dm == null) { |
| 195 | // may be null early in system startup |
| 196 | return null; |
| 197 | } |
Wale Ogunwale | 2669851 | 2015-06-05 16:55:33 -0700 | [diff] [blame] | 198 | final Display display = dm.getCompatibleDisplay(displayId, key.second); |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 199 | if (display != null) { |
| 200 | mDisplays.put(key, new WeakReference<>(display)); |
| 201 | } |
| 202 | return display; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | /** |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 207 | * Creates an AssetManager from the paths within the ResourcesKey. |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 208 | * |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 209 | * This can be overridden in tests so as to avoid creating a real AssetManager with |
| 210 | * real APK paths. |
| 211 | * @param key The key containing the resource paths to add to the AssetManager. |
| 212 | * @return a new AssetManager. |
| 213 | */ |
| 214 | @VisibleForTesting |
| 215 | protected AssetManager createAssetManager(@NonNull final ResourcesKey key) { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 216 | AssetManager assets = new AssetManager(); |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 217 | |
Adam Lesinski | 54130de | 2014-08-20 10:49:13 -0700 | [diff] [blame] | 218 | // resDir can be null if the 'android' package is creating a new Resources object. |
| 219 | // This is fine, since each AssetManager automatically loads the 'android' package |
| 220 | // already. |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 221 | if (key.mResDir != null) { |
| 222 | if (assets.addAssetPath(key.mResDir) == 0) { |
Adam Lesinski | 54130de | 2014-08-20 10:49:13 -0700 | [diff] [blame] | 223 | return null; |
| 224 | } |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 225 | } |
| 226 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 227 | if (key.mSplitResDirs != null) { |
| 228 | for (final String splitResDir : key.mSplitResDirs) { |
Jeff Sharkey | 8a4c972 | 2014-06-16 13:48:42 -0700 | [diff] [blame] | 229 | if (assets.addAssetPath(splitResDir) == 0) { |
| 230 | return null; |
| 231 | } |
| 232 | } |
| 233 | } |
| 234 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 235 | if (key.mOverlayDirs != null) { |
| 236 | for (final String idmapPath : key.mOverlayDirs) { |
MÃ¥rten Kongstad | 48d2232 | 2014-01-31 14:43:27 +0100 | [diff] [blame] | 237 | assets.addOverlayPath(idmapPath); |
| 238 | } |
| 239 | } |
| 240 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 241 | if (key.mLibDirs != null) { |
| 242 | for (final String libDir : key.mLibDirs) { |
Adam Lesinski | 1dd50c5 | 2015-03-16 15:10:56 -0700 | [diff] [blame] | 243 | if (libDir.endsWith(".apk")) { |
| 244 | // Avoid opening files we know do not have resources, |
| 245 | // like code-only .jar files. |
| 246 | if (assets.addAssetPath(libDir) == 0) { |
| 247 | Log.w(TAG, "Asset path '" + libDir + |
| 248 | "' does not exist or contains no resources."); |
| 249 | } |
Adam Lesinski | de898ff | 2014-01-29 18:20:45 -0800 | [diff] [blame] | 250 | } |
| 251 | } |
| 252 | } |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 253 | return assets; |
| 254 | } |
Adam Lesinski | de898ff | 2014-01-29 18:20:45 -0800 | [diff] [blame] | 255 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 256 | private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 257 | Configuration config; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 258 | final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 259 | final boolean hasOverrideConfig = key.hasOverrideConfiguration(); |
| 260 | if (!isDefaultDisplay || hasOverrideConfig) { |
| 261 | config = new Configuration(getConfiguration()); |
| 262 | if (!isDefaultDisplay) { |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 263 | applyNonDefaultDisplayMetricsToConfiguration(dm, config); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 264 | } |
| 265 | if (hasOverrideConfig) { |
| 266 | config.updateFrom(key.mOverrideConfiguration); |
Wale Ogunwale | 60454db | 2015-01-23 16:05:07 -0800 | [diff] [blame] | 267 | if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 268 | } |
| 269 | } else { |
| 270 | config = getConfiguration(); |
| 271 | } |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 272 | return config; |
| 273 | } |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 274 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 275 | |
| 276 | private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { |
| 277 | AssetManager assets = createAssetManager(key); |
| 278 | DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId); |
| 279 | Configuration config = generateConfig(key, dm); |
| 280 | ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo); |
| 281 | if (DEBUG) { |
| 282 | Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); |
| 283 | } |
| 284 | return impl; |
| 285 | } |
| 286 | |
| 287 | /** |
| 288 | * Finds a cached ResourcesImpl object that matches the given ResourcesKey. |
| 289 | * |
| 290 | * @param key The key to match. |
| 291 | * @return a ResourcesImpl if the key matches a cache entry, null otherwise. |
| 292 | */ |
| 293 | private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) { |
| 294 | WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key); |
| 295 | ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; |
| 296 | if (impl != null && impl.getAssets().isUpToDate()) { |
| 297 | return impl; |
| 298 | } |
| 299 | return null; |
| 300 | } |
| 301 | |
| 302 | /** |
| 303 | * Find the ResourcesKey that this ResourcesImpl object is associated with. |
| 304 | * @return the ResourcesKey or null if none was found. |
| 305 | */ |
| 306 | private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) { |
| 307 | final int refCount = mResourceImpls.size(); |
| 308 | for (int i = 0; i < refCount; i++) { |
| 309 | WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); |
| 310 | ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; |
| 311 | if (impl != null && resourceImpl == impl) { |
| 312 | return mResourceImpls.keyAt(i); |
| 313 | } |
| 314 | } |
| 315 | return null; |
| 316 | } |
| 317 | |
| 318 | /** |
| 319 | * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist |
| 320 | * or the class loader is different. |
| 321 | */ |
| 322 | private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, |
| 323 | @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) { |
| 324 | // This is a request tied to an Activity, meaning we will need to update all |
| 325 | // Activity related Resources to match this configuration. |
| 326 | WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken); |
| 327 | Resources resources = weakResourceRef != null ? weakResourceRef.get() : null; |
| 328 | if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) { |
| 329 | resources = new Resources(classLoader); |
| 330 | mActivityResourceReferences.put(activityToken, new WeakReference<>(resources)); |
| 331 | if (DEBUG) { |
| 332 | Slog.d(TAG, "- creating new ref=" + resources); |
| 333 | } |
| 334 | } else { |
| 335 | if (DEBUG) { |
| 336 | Slog.d(TAG, "- using existing ref=" + resources); |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | if (resources.getImpl() != impl) { |
| 341 | if (DEBUG) { |
| 342 | Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); |
| 343 | } |
| 344 | |
| 345 | // Setting an impl is expensive because we update all ThemeImpl references. |
| 346 | // too. |
| 347 | resources.setImpl(impl); |
| 348 | } |
| 349 | return resources; |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * Gets an existing Resources object if the class loader and ResourcesImpl are the same, |
| 354 | * otherwise creates a new Resources object. |
| 355 | */ |
| 356 | private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader, |
| 357 | @NonNull ResourcesImpl impl) { |
| 358 | // Find an existing Resources that has this ResourcesImpl set. |
| 359 | final int refCount = mResourceReferences.size(); |
| 360 | for (int i = 0; i < refCount; i++) { |
| 361 | WeakReference<Resources> weakResourceRef = mResourceReferences.get(i); |
| 362 | Resources resources = weakResourceRef != null ? weakResourceRef.get() : null; |
| 363 | if (resources != null && |
| 364 | Objects.equals(resources.getClassLoader(), classLoader) && |
| 365 | resources.getImpl() == impl) { |
| 366 | if (DEBUG) { |
| 367 | Slog.d(TAG, "- using existing ref=" + resources); |
| 368 | } |
| 369 | return resources; |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | // Create a new Resources reference and use the existing ResourcesImpl object. |
| 374 | Resources resources = new Resources(classLoader); |
| 375 | resources.setImpl(impl); |
| 376 | mResourceReferences.add(new WeakReference<>(resources)); |
| 377 | if (DEBUG) { |
| 378 | Slog.d(TAG, "- creating new ref=" + resources); |
| 379 | Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); |
| 380 | } |
| 381 | return resources; |
| 382 | } |
| 383 | |
| 384 | /** |
| 385 | * Gets or creates a new Resources object associated with the IBinder token. References returned |
| 386 | * by this method live as long as the Activity, meaning they can be cached and used by the |
| 387 | * Activity even after a configuration change. If any other parameter is changed |
| 388 | * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object |
| 389 | * is updated and handed back to the caller. However, changing the class loader will result in a |
| 390 | * new Resources object. |
| 391 | * <p/> |
| 392 | * If activityToken is null, a cached Resources object will be returned if it matches the |
| 393 | * input parameters. Otherwise a new Resources object that satisfies these parameters is |
| 394 | * returned. |
| 395 | * |
| 396 | * @param activityToken Represents an Activity. If null, global resources are assumed. |
| 397 | * @param resDir The base resource path. Can be null (only framework resources will be loaded). |
| 398 | * @param splitResDirs An array of split resource paths. Can be null. |
| 399 | * @param overlayDirs An array of overlay paths. Can be null. |
| 400 | * @param libDirs An array of resource library paths. Can be null. |
| 401 | * @param displayId The ID of the display for which to create the resources. |
| 402 | * @param overrideConfig The configuration to apply on top of the base configuration. Can be |
| 403 | * null. Mostly used with Activities that are in multi-window which may override width and |
| 404 | * height properties from the base config. |
| 405 | * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is |
| 406 | * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. |
| 407 | * @param classLoader The class loader to use when inflating Resources. If null, the |
| 408 | * {@link ClassLoader#getSystemClassLoader()} is used. |
| 409 | * @return a Resources object from which to access resources. |
| 410 | */ |
| 411 | public Resources getResources(@Nullable IBinder activityToken, |
| 412 | @Nullable String resDir, |
| 413 | @Nullable String[] splitResDirs, |
| 414 | @Nullable String[] overlayDirs, |
| 415 | @Nullable String[] libDirs, |
| 416 | int displayId, |
| 417 | @Nullable Configuration overrideConfig, |
| 418 | @NonNull CompatibilityInfo compatInfo, |
| 419 | @Nullable ClassLoader classLoader) { |
| 420 | final ResourcesKey key = new ResourcesKey( |
| 421 | resDir, |
| 422 | splitResDirs, |
| 423 | overlayDirs, |
| 424 | libDirs, |
| 425 | displayId, |
| 426 | overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy |
| 427 | compatInfo); |
| 428 | |
| 429 | classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); |
| 430 | |
| 431 | final boolean findSystemLocales; |
| 432 | final boolean hasNonSystemLocales; |
| 433 | synchronized (this) { |
| 434 | findSystemLocales = (mSystemLocales.length == 0); |
| 435 | hasNonSystemLocales = mHasNonSystemLocales; |
| 436 | |
| 437 | if (DEBUG) { |
| 438 | Throwable here = new Throwable(); |
| 439 | here.fillInStackTrace(); |
| 440 | Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); |
| 441 | } |
| 442 | |
| 443 | if (activityToken != null) { |
| 444 | ResourcesImpl resourcesImpl = findResourcesImplForKey(key); |
| 445 | if (resourcesImpl != null) { |
| 446 | if (DEBUG) { |
| 447 | Slog.d(TAG, "- using existing impl=" + resourcesImpl); |
| 448 | } |
| 449 | return getOrCreateResourcesForActivityLocked(activityToken, classLoader, |
| 450 | resourcesImpl); |
| 451 | } |
| 452 | |
| 453 | // We will create the ResourcesImpl object outside of holding this lock. |
| 454 | |
| 455 | } else { |
| 456 | // Clean up any dead references so they don't pile up. |
| 457 | ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); |
| 458 | |
| 459 | // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl |
| 460 | ResourcesImpl resourcesImpl = findResourcesImplForKey(key); |
| 461 | if (resourcesImpl != null) { |
| 462 | if (DEBUG) { |
| 463 | Slog.d(TAG, "- using existing impl=" + resourcesImpl); |
| 464 | } |
| 465 | return getOrCreateResourcesLocked(classLoader, resourcesImpl); |
| 466 | } |
| 467 | |
| 468 | // We will create the ResourcesImpl object outside of holding this lock. |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now. |
| 473 | ResourcesImpl resourcesImpl = createResourcesImpl(key); |
| 474 | |
| 475 | final String[] systemLocales = findSystemLocales |
| 476 | ? AssetManager.getSystem().getLocales() : null; |
| 477 | final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales(); |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 478 | // Avoid checking for non-pseudo-locales if we already know there were some from a previous |
| 479 | // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, |
| 480 | // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be |
| 481 | // able to affect mHasNonSystemLocales. |
| 482 | final boolean isPseudoLocalesOnly = hasNonSystemLocales || |
| 483 | LocaleList.isPseudoLocalesOnly(nonSystemLocales); |
| 484 | |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 485 | synchronized (this) { |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 486 | if (mSystemLocales.length == 0) { |
| 487 | mSystemLocales = systemLocales; |
| 488 | } |
| 489 | mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); |
| 490 | mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 491 | |
| 492 | ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key); |
| 493 | if (existingResourcesImpl != null) { |
| 494 | if (DEBUG) { |
| 495 | Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl |
| 496 | + " new impl=" + resourcesImpl); |
| 497 | } |
| 498 | resourcesImpl.getAssets().close(); |
| 499 | resourcesImpl = existingResourcesImpl; |
| 500 | } else { |
| 501 | // Add this ResourcesImpl to the cache. |
| 502 | mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); |
| 503 | } |
| 504 | |
| 505 | final Resources resources; |
| 506 | if (activityToken != null) { |
| 507 | resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, |
| 508 | resourcesImpl); |
| 509 | } else { |
| 510 | resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); |
| 511 | } |
| 512 | return resources; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 513 | } |
| 514 | } |
| 515 | |
Todd Kennedy | 39bfee5 | 2016-02-24 10:28:21 -0800 | [diff] [blame] | 516 | /** |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 517 | * Updates an Activity's Resources object with overrideConfig. The Resources object |
| 518 | * that was previously returned by |
| 519 | * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration, |
| 520 | * CompatibilityInfo, ClassLoader)} is |
| 521 | * still valid and will have the updated configuration. |
| 522 | * @param activityToken The Activity token. |
| 523 | * @param overrideConfig The configuration override to update. |
Todd Kennedy | 39bfee5 | 2016-02-24 10:28:21 -0800 | [diff] [blame] | 524 | */ |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 525 | public void updateResourcesForActivity(@NonNull IBinder activityToken, |
| 526 | @Nullable Configuration overrideConfig) { |
| 527 | final ClassLoader classLoader; |
| 528 | final ResourcesKey oldKey; |
| 529 | synchronized (this) { |
| 530 | // Extract the ResourcesKey that was last used to create the Resources for this |
| 531 | // activity. |
| 532 | WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken); |
| 533 | final Resources resources = weakResRef != null ? weakResRef.get() : null; |
| 534 | if (resources == null) { |
| 535 | Slog.e(TAG, "can't update resources for uncached activity " + activityToken); |
| 536 | return; |
| 537 | } |
| 538 | |
| 539 | classLoader = resources.getClassLoader(); |
| 540 | oldKey = findKeyForResourceImpl(resources.getImpl()); |
| 541 | if (oldKey == null) { |
| 542 | Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl()); |
| 543 | return; |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | // Update the Resources object with the new override config and all of the existing |
| 548 | // settings. |
| 549 | getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs, |
| 550 | oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo, |
| 551 | classLoader); |
Todd Kennedy | 39bfee5 | 2016-02-24 10:28:21 -0800 | [diff] [blame] | 552 | } |
| 553 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 554 | /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) { |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 555 | final int bestLocale; |
| 556 | if (mHasNonSystemLocales) { |
| 557 | bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); |
| 558 | } else { |
| 559 | // We fallback to system locales if there was no locale specifically supported by the |
| 560 | // assets. This is to properly support apps that only rely on the shared system assets |
| 561 | // and don't need assets of their own. |
| 562 | bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mSystemLocales); |
| 563 | } |
| 564 | // set it for Java, this also affects newly created Resources |
| 565 | LocaleList.setDefault(locales, bestLocale); |
| 566 | } |
| 567 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 568 | public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, |
| 569 | @Nullable CompatibilityInfo compat) { |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 570 | if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { |
Wale Ogunwale | 60454db | 2015-01-23 16:05:07 -0800 | [diff] [blame] | 571 | if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 572 | + mResConfiguration.seq + ", newSeq=" + config.seq); |
| 573 | return false; |
| 574 | } |
| 575 | int changes = mResConfiguration.updateFrom(config); |
Wale Ogunwale | 7c72668 | 2015-02-06 17:34:28 -0800 | [diff] [blame] | 576 | // Things might have changed in display manager, so clear the cached displays. |
| 577 | mDisplays.clear(); |
| 578 | DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 579 | |
| 580 | if (compat != null && (mResCompatibilityInfo == null || |
| 581 | !mResCompatibilityInfo.equals(compat))) { |
| 582 | mResCompatibilityInfo = compat; |
| 583 | changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT |
| 584 | | ActivityInfo.CONFIG_SCREEN_SIZE |
| 585 | | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; |
| 586 | } |
| 587 | |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 588 | Configuration localeAdjustedConfig = config; |
| 589 | final LocaleList configLocales = config.getLocales(); |
| 590 | if (!configLocales.isEmpty()) { |
| 591 | setDefaultLocalesLocked(configLocales); |
| 592 | final LocaleList adjustedLocales = LocaleList.getAdjustedDefault(); |
| 593 | if (adjustedLocales != configLocales) { // has the same result as .equals() in this case |
| 594 | // The first locale in the list was not chosen. So we create a modified |
| 595 | // configuration with the adjusted locales (which moves the chosen locale to the |
| 596 | // front). |
| 597 | localeAdjustedConfig = new Configuration(); |
| 598 | localeAdjustedConfig.setTo(config); |
| 599 | localeAdjustedConfig.setLocales(adjustedLocales); |
| 600 | // Also adjust the locale list in mResConfiguration, so that the Resources created |
| 601 | // later would have the same locale list. |
| 602 | if (!mResConfiguration.getLocales().equals(adjustedLocales)) { |
| 603 | mResConfiguration.setLocales(adjustedLocales); |
| 604 | changes |= ActivityInfo.CONFIG_LOCALE; |
| 605 | } |
| 606 | } |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 607 | } |
| 608 | |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 609 | Resources.updateSystemConfiguration(localeAdjustedConfig, defaultDisplayMetrics, compat); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 610 | |
| 611 | ApplicationPackageManager.configurationChanged(); |
| 612 | //Slog.i(TAG, "Configuration changed in " + currentPackageName()); |
| 613 | |
| 614 | Configuration tmpConfig = null; |
| 615 | |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 616 | for (int i = mResourceImpls.size() - 1; i >= 0; i--) { |
| 617 | ResourcesKey key = mResourceImpls.keyAt(i); |
| 618 | ResourcesImpl r = mResourceImpls.valueAt(i).get(); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 619 | if (r != null) { |
Wale Ogunwale | 60454db | 2015-01-23 16:05:07 -0800 | [diff] [blame] | 620 | if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 621 | + r + " config to: " + localeAdjustedConfig); |
Dianne Hackborn | add005c | 2013-07-17 18:43:12 -0700 | [diff] [blame] | 622 | int displayId = key.mDisplayId; |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 623 | boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); |
| 624 | DisplayMetrics dm = defaultDisplayMetrics; |
Dianne Hackborn | add005c | 2013-07-17 18:43:12 -0700 | [diff] [blame] | 625 | final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 626 | if (!isDefaultDisplay || hasOverrideConfiguration) { |
| 627 | if (tmpConfig == null) { |
| 628 | tmpConfig = new Configuration(); |
| 629 | } |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 630 | tmpConfig.setTo(localeAdjustedConfig); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 631 | if (!isDefaultDisplay) { |
| 632 | dm = getDisplayMetricsLocked(displayId); |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 633 | applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 634 | } |
| 635 | if (hasOverrideConfiguration) { |
Dianne Hackborn | add005c | 2013-07-17 18:43:12 -0700 | [diff] [blame] | 636 | tmpConfig.updateFrom(key.mOverrideConfiguration); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 637 | } |
| 638 | r.updateConfiguration(tmpConfig, dm, compat); |
| 639 | } else { |
Roozbeh Pournader | 834641b | 2016-01-23 22:34:57 -0800 | [diff] [blame] | 640 | r.updateConfiguration(localeAdjustedConfig, dm, compat); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 641 | } |
| 642 | //Slog.i(TAG, "Updated app resources " + v.getKey() |
| 643 | // + " " + r + ": " + r.getConfiguration()); |
| 644 | } else { |
| 645 | //Slog.i(TAG, "Removing old resources " + v.getKey()); |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 646 | mResourceImpls.removeAt(i); |
Craig Mautner | 88c0589 | 2013-06-28 09:47:45 -0700 | [diff] [blame] | 647 | } |
| 648 | } |
| 649 | |
| 650 | return changes != 0; |
| 651 | } |
Adam Lesinski | 082614c | 2016-03-04 14:33:47 -0800 | [diff] [blame] | 652 | } |