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 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); 128 handleConfigurationChanged(config, null /* compat */); 129 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 130 } 131 132 /** 133 * Update the configuration to latest. 134 * @param compat The new compatibility information. 135 */ handleConfigurationChanged(@onNull CompatibilityInfo compat)136 void handleConfigurationChanged(@NonNull CompatibilityInfo compat) { 137 handleConfigurationChanged(mConfiguration, compat); 138 WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); 139 } 140 141 /** 142 * Update the configuration to latest. 143 * @param config The new configuration. 144 * @param compat The new compatibility information. 145 */ handleConfigurationChanged(@ullable Configuration config, @Nullable CompatibilityInfo compat)146 void handleConfigurationChanged(@Nullable Configuration config, 147 @Nullable CompatibilityInfo compat) { 148 int configDiff; 149 boolean equivalent; 150 151 // Get theme outside of synchronization to avoid nested lock. 152 final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); 153 final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate(); 154 final Resources.Theme systemUiTheme = 155 systemUiContext != null ? systemUiContext.getTheme() : null; 156 synchronized (mResourcesManager) { 157 if (mPendingConfiguration != null) { 158 if (!mPendingConfiguration.isOtherSeqNewer(config)) { 159 config = mPendingConfiguration; 160 updateDefaultDensity(config.densityDpi); 161 } 162 mPendingConfiguration = null; 163 } 164 165 if (config == null) { 166 return; 167 } 168 169 // This flag tracks whether the new configuration is fundamentally equivalent to the 170 // existing configuration. This is necessary to determine whether non-activity callbacks 171 // should receive notice when the only changes are related to non-public fields. 172 // We do not gate calling {@link #performActivityConfigurationChanged} based on this 173 // flag as that method uses the same check on the activity config override as well. 174 equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); 175 176 if (DEBUG_CONFIGURATION) { 177 Slog.v(TAG, "Handle configuration changed: " + config); 178 } 179 180 final Application app = mActivityThread.getApplication(); 181 final Resources appResources = app.getResources(); 182 mResourcesManager.applyConfigurationToResources(config, compat); 183 updateLocaleListFromAppContext(app.getApplicationContext()); 184 185 if (mConfiguration == null) { 186 mConfiguration = new Configuration(); 187 } 188 if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { 189 return; 190 } 191 192 configDiff = mConfiguration.updateFrom(config); 193 config = applyCompatConfiguration(); 194 HardwareRenderer.sendDeviceConfigurationForDebugging(config); 195 196 if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { 197 systemTheme.rebase(); 198 } 199 200 if (systemUiTheme != null 201 && (systemUiTheme.getChangingConfigurations() & configDiff) != 0) { 202 systemUiTheme.rebase(); 203 } 204 } 205 206 final ArrayList<ComponentCallbacks2> callbacks = 207 mActivityThread.collectComponentCallbacks(false /* includeUiContexts */); 208 209 freeTextLayoutCachesIfNeeded(configDiff); 210 211 if (callbacks != null) { 212 final int size = callbacks.size(); 213 for (int i = 0; i < size; i++) { 214 ComponentCallbacks2 cb = callbacks.get(i); 215 if (!equivalent) { 216 performConfigurationChanged(cb, config); 217 } 218 } 219 } 220 } 221 222 /** 223 * Decides whether to update a component's configuration and whether to inform it. 224 * @param cb The component callback to notify of configuration change. 225 * @param newConfig The new configuration. 226 */ performConfigurationChanged(@onNull ComponentCallbacks2 cb, @NonNull Configuration newConfig)227 void performConfigurationChanged(@NonNull ComponentCallbacks2 cb, 228 @NonNull Configuration newConfig) { 229 // ContextThemeWrappers may override the configuration for that context. We must check and 230 // apply any overrides defined. 231 Configuration contextThemeWrapperOverrideConfig = null; 232 if (cb instanceof ContextThemeWrapper) { 233 final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb; 234 contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration(); 235 } 236 237 // Apply the ContextThemeWrapper override if necessary. 238 // NOTE: Make sure the configurations are not modified, as they are treated as immutable 239 // in many places. 240 final Configuration configToReport = createNewConfigAndUpdateIfNotNull( 241 newConfig, contextThemeWrapperOverrideConfig); 242 cb.onConfigurationChanged(configToReport); 243 } 244 245 /** Update default density. */ updateDefaultDensity(int densityDpi)246 void updateDefaultDensity(int densityDpi) { 247 if (!mActivityThread.isInDensityCompatMode() 248 && densityDpi != Configuration.DENSITY_DPI_UNDEFINED 249 && densityDpi != DisplayMetrics.DENSITY_DEVICE) { 250 DisplayMetrics.DENSITY_DEVICE = densityDpi; 251 Bitmap.setDefaultDensity(densityDpi); 252 } 253 } 254 255 /** Get current default display dpi. This is only done to maintain @UnsupportedAppUsage. */ getCurDefaultDisplayDpi()256 int getCurDefaultDisplayDpi() { 257 return mConfiguration.densityDpi; 258 } 259 260 /** 261 * The LocaleList set for the app's resources may have been shuffled so that the preferred 262 * Locale is at position 0. We must find the index of this preferred Locale in the 263 * original LocaleList. 264 */ updateLocaleListFromAppContext(@onNull Context context)265 void updateLocaleListFromAppContext(@NonNull Context context) { 266 final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); 267 final LocaleList newLocaleList = mResourcesManager.getConfiguration().getLocales(); 268 final int newLocaleListSize = newLocaleList.size(); 269 for (int i = 0; i < newLocaleListSize; i++) { 270 if (bestLocale.equals(newLocaleList.get(i))) { 271 LocaleList.setDefault(newLocaleList, i); 272 return; 273 } 274 } 275 276 // The app may have overridden the LocaleList with its own Locale 277 // (not present in the available list). Push the chosen Locale 278 // to the front of the list. 279 LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList)); 280 } 281 282 /** 283 * Creates a new Configuration only if override would modify base. Otherwise returns base. 284 * @param base The base configuration. 285 * @param override The update to apply to the base configuration. Can be null. 286 * @return A Configuration representing base with override applied. 287 */ createNewConfigAndUpdateIfNotNull(@onNull Configuration base, @Nullable Configuration override)288 static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base, 289 @Nullable Configuration override) { 290 if (override == null) { 291 return base; 292 } 293 Configuration newConfig = new Configuration(base); 294 newConfig.updateFrom(override); 295 return newConfig; 296 } 297 298 } 299