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