1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.app; 18 19 import static android.app.ActivityThread.DEBUG_CONFIGURATION; 20 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.ComponentCallbacks2; 25 import android.content.Context; 26 import android.content.res.CompatibilityInfo; 27 import android.content.res.Configuration; 28 import android.content.res.Resources; 29 import android.graphics.Bitmap; 30 import android.graphics.HardwareRenderer; 31 import android.os.LocaleList; 32 import android.os.Trace; 33 import android.util.DisplayMetrics; 34 import android.util.Slog; 35 import android.view.ContextThemeWrapper; 36 import android.view.WindowManagerGlobal; 37 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.util.ArrayList; 41 import java.util.Locale; 42 43 /** 44 * A client side controller to handle process level configuration changes. 45 * @hide 46 */ 47 class ConfigurationController { 48 private static final String TAG = "ConfigurationController"; 49 50 private final ActivityThreadInternal mActivityThread; 51 52 private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); 53 54 @GuardedBy("mResourcesManager") 55 private @Nullable Configuration mPendingConfiguration; 56 private @Nullable Configuration mCompatConfiguration; 57 private @Nullable Configuration mConfiguration; 58 ConfigurationController(@onNull ActivityThreadInternal activityThread)59 ConfigurationController(@NonNull ActivityThreadInternal activityThread) { 60 mActivityThread = activityThread; 61 } 62 63 /** Update the pending configuration. */ updatePendingConfiguration(@onNull Configuration config)64 Configuration updatePendingConfiguration(@NonNull Configuration config) { 65 synchronized (mResourcesManager) { 66 if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) { 67 mPendingConfiguration = config; 68 return mPendingConfiguration; 69 } 70 } 71 return null; 72 } 73 74 /** Get the pending configuration. */ getPendingConfiguration(boolean clearPending)75 Configuration getPendingConfiguration(boolean clearPending) { 76 Configuration outConfig = null; 77 synchronized (mResourcesManager) { 78 if (mPendingConfiguration != null) { 79 outConfig = mPendingConfiguration; 80 if (clearPending) { 81 mPendingConfiguration = null; 82 } 83 } 84 } 85 return outConfig; 86 } 87 88 /** Set the compatibility configuration. */ setCompatConfiguration(@onNull Configuration config)89 void setCompatConfiguration(@NonNull Configuration config) { 90 mCompatConfiguration = new Configuration(config); 91 } 92 93 /** Get the compatibility configuration. */ getCompatConfiguration()94 Configuration getCompatConfiguration() { 95 return mCompatConfiguration; 96 } 97 98 /** Apply the global compatibility configuration. */ applyCompatConfiguration()99 final Configuration applyCompatConfiguration() { 100 Configuration config = mConfiguration; 101 final int displayDensity = config.densityDpi; 102 if (mCompatConfiguration == null) { 103 mCompatConfiguration = new Configuration(); 104 } 105 mCompatConfiguration.setTo(mConfiguration); 106 if (mResourcesManager.applyCompatConfiguration(displayDensity, mCompatConfiguration)) { 107 config = mCompatConfiguration; 108 } 109 return config; 110 } 111 112 /** Set the configuration. */ setConfiguration(@onNull Configuration config)113 void setConfiguration(@NonNull Configuration config) { 114 mConfiguration = new Configuration(config); 115 } 116 117 /** Get current configuration. */ getConfiguration()118 Configuration getConfiguration() { 119 return mConfiguration; 120 } 121 122 /** 123 * Update the configuration to latest. 124 * @param config The new configuration. 125 */ handleConfigurationChanged(@onNull Configuration config)126 void handleConfigurationChanged(@NonNull Configuration config) { 127 if (mActivityThread.isCachedProcessState()) { 128 updatePendingConfiguration(config); 129 // If the process is in a cached state, delay the handling until the process is no 130 // longer cached. 131 return; 132 } 133 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); 134 handleConfigurationChanged(config, null /* compat */); 135 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 136 } 137 138 /** 139 * Update the configuration to latest. 140 * @param compat The new compatibility information. 141 */ handleConfigurationChanged(@onNull CompatibilityInfo compat)142 void handleConfigurationChanged(@NonNull CompatibilityInfo compat) { 143 handleConfigurationChanged(mConfiguration, compat); 144 WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); 145 } 146 147 /** 148 * Update the configuration to latest. 149 * @param config The new configuration. 150 * @param compat The new compatibility information. 151 */ handleConfigurationChanged(@ullable Configuration config, @Nullable CompatibilityInfo compat)152 void handleConfigurationChanged(@Nullable Configuration config, 153 @Nullable CompatibilityInfo compat) { 154 int configDiff; 155 boolean equivalent; 156 157 // Get theme outside of synchronization to avoid nested lock. 158 final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); 159 final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate(); 160 final Resources.Theme systemUiTheme = 161 systemUiContext != null ? systemUiContext.getTheme() : null; 162 synchronized (mResourcesManager) { 163 if (mPendingConfiguration != null) { 164 if (!mPendingConfiguration.isOtherSeqNewer(config)) { 165 config = mPendingConfiguration; 166 updateDefaultDensity(config.densityDpi); 167 } 168 mPendingConfiguration = null; 169 } 170 171 if (config == null) { 172 return; 173 } 174 175 // This flag tracks whether the new configuration is fundamentally equivalent to the 176 // existing configuration. This is necessary to determine whether non-activity callbacks 177 // should receive notice when the only changes are related to non-public fields. 178 // We do not gate calling {@link #performActivityConfigurationChanged} based on this 179 // flag as that method uses the same check on the activity config override as well. 180 equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); 181 182 if (DEBUG_CONFIGURATION) { 183 Slog.v(TAG, "Handle configuration changed: " + config); 184 } 185 186 final Application app = mActivityThread.getApplication(); 187 final Resources appResources = app.getResources(); 188 if (appResources.hasOverrideDisplayAdjustments()) { 189 // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments, 190 // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated 191 // configuration also needs to set to the adjustments for consistency. 192 appResources.getDisplayAdjustments().getConfiguration().updateFrom(config); 193 } 194 mResourcesManager.applyConfigurationToResources(config, compat, 195 appResources.getDisplayAdjustments()); 196 updateLocaleListFromAppContext(app.getApplicationContext()); 197 198 if (mConfiguration == null) { 199 mConfiguration = new Configuration(); 200 } 201 if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { 202 return; 203 } 204 205 configDiff = mConfiguration.updateFrom(config); 206 config = applyCompatConfiguration(); 207 HardwareRenderer.sendDeviceConfigurationForDebugging(config); 208 209 if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { 210 systemTheme.rebase(); 211 } 212 213 if (systemUiTheme != null 214 && (systemUiTheme.getChangingConfigurations() & configDiff) != 0) { 215 systemUiTheme.rebase(); 216 } 217 } 218 219 final ArrayList<ComponentCallbacks2> callbacks = 220 mActivityThread.collectComponentCallbacks(false /* includeUiContexts */); 221 222 freeTextLayoutCachesIfNeeded(configDiff); 223 224 if (callbacks != null) { 225 final int size = callbacks.size(); 226 for (int i = 0; i < size; i++) { 227 ComponentCallbacks2 cb = callbacks.get(i); 228 if (!equivalent) { 229 performConfigurationChanged(cb, config); 230 } 231 } 232 } 233 } 234 235 /** 236 * Decides whether to update a component's configuration and whether to inform it. 237 * @param cb The component callback to notify of configuration change. 238 * @param newConfig The new configuration. 239 */ performConfigurationChanged(@onNull ComponentCallbacks2 cb, @NonNull Configuration newConfig)240 void performConfigurationChanged(@NonNull ComponentCallbacks2 cb, 241 @NonNull Configuration newConfig) { 242 // ContextThemeWrappers may override the configuration for that context. We must check and 243 // apply any overrides defined. 244 Configuration contextThemeWrapperOverrideConfig = null; 245 if (cb instanceof ContextThemeWrapper) { 246 final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb; 247 contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration(); 248 } 249 250 // Apply the ContextThemeWrapper override if necessary. 251 // NOTE: Make sure the configurations are not modified, as they are treated as immutable 252 // in many places. 253 final Configuration configToReport = createNewConfigAndUpdateIfNotNull( 254 newConfig, contextThemeWrapperOverrideConfig); 255 cb.onConfigurationChanged(configToReport); 256 } 257 258 /** Update default density. */ updateDefaultDensity(int densityDpi)259 void updateDefaultDensity(int densityDpi) { 260 if (!mActivityThread.isInDensityCompatMode() 261 && densityDpi != Configuration.DENSITY_DPI_UNDEFINED 262 && densityDpi != DisplayMetrics.DENSITY_DEVICE) { 263 DisplayMetrics.DENSITY_DEVICE = densityDpi; 264 Bitmap.setDefaultDensity(densityDpi); 265 } 266 } 267 268 /** Get current default display dpi. This is only done to maintain @UnsupportedAppUsage. */ getCurDefaultDisplayDpi()269 int getCurDefaultDisplayDpi() { 270 return mConfiguration.densityDpi; 271 } 272 273 /** 274 * The LocaleList set for the app's resources may have been shuffled so that the preferred 275 * Locale is at position 0. We must find the index of this preferred Locale in the 276 * original LocaleList. 277 */ updateLocaleListFromAppContext(@onNull Context context)278 void updateLocaleListFromAppContext(@NonNull Context context) { 279 final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); 280 final LocaleList newLocaleList = mResourcesManager.getConfiguration().getLocales(); 281 final int newLocaleListSize = newLocaleList.size(); 282 for (int i = 0; i < newLocaleListSize; i++) { 283 if (bestLocale.equals(newLocaleList.get(i))) { 284 LocaleList.setDefault(newLocaleList, i); 285 return; 286 } 287 } 288 289 // The app may have overridden the LocaleList with its own Locale 290 // (not present in the available list). Push the chosen Locale 291 // to the front of the list. 292 LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList)); 293 } 294 295 /** 296 * Creates a new Configuration only if override would modify base. Otherwise returns base. 297 * @param base The base configuration. 298 * @param override The update to apply to the base configuration. Can be null. 299 * @return A Configuration representing base with override applied. 300 */ createNewConfigAndUpdateIfNotNull(@onNull Configuration base, @Nullable Configuration override)301 static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base, 302 @Nullable Configuration override) { 303 if (override == null) { 304 return base; 305 } 306 Configuration newConfig = new Configuration(base); 307 newConfig.updateFrom(override); 308 return newConfig; 309 } 310 311 } 312