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