diff options
| author | 2021-03-09 01:58:58 +0000 | |
|---|---|---|
| committer | 2021-03-09 01:58:58 +0000 | |
| commit | 6cd1cb58f7ddd3534aea8878d9ca13bd3aac01eb (patch) | |
| tree | 63ca957bdb8ff09a0ce2131de723e29f25be687c | |
| parent | e3c413337457d2b0b7d62071f4125d6e310f13ce (diff) | |
| parent | 3dbe4cd5b2a8443256f56e8dae81e649b369f654 (diff) | |
Merge "Introduce ConfigurationController" into sc-dev
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 340 | ||||
| -rw-r--r-- | core/java/android/app/ActivityThreadInternal.java | 42 | ||||
| -rw-r--r-- | core/java/android/app/ConfigurationController.java | 343 |
3 files changed, 471 insertions, 254 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e7751b861037..0b5958695dff 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -17,6 +17,8 @@ package android.app; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; +import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull; +import static android.app.ConfigurationController.freeTextLayoutCachesIfNeeded; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; @@ -79,7 +81,6 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.content.res.Resources.Theme; import android.content.res.loader.ResourcesLoader; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; @@ -162,7 +163,6 @@ import android.util.SuperNotCalledException; import android.util.UtilConfig; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; -import android.view.ContextThemeWrapper; import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayAdjustments.FixedRotationAdjustments; @@ -228,7 +228,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TimeZone; @@ -251,7 +250,8 @@ final class RemoteServiceException extends AndroidRuntimeException { * * {@hide} */ -public final class ActivityThread extends ClientTransactionHandler { +public final class ActivityThread extends ClientTransactionHandler + implements ActivityThreadInternal { /** @hide */ public static final String TAG = "ActivityThread"; private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565; @@ -363,13 +363,15 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage AppBindData mBoundApplication; Profiler mProfiler; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553, + publicAlternatives = "Use {@code Context#getResources()#getConfiguration()#densityDpi} " + + "instead.") int mCurDefaultDisplayDpi; @UnsupportedAppUsage boolean mDensityCompatMode; - @UnsupportedAppUsage + @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R, + publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.") Configuration mConfiguration; - Configuration mCompatConfiguration; @UnsupportedAppUsage Application mInitialApplication; @UnsupportedAppUsage @@ -420,7 +422,8 @@ public final class ActivityThread extends ClientTransactionHandler { @GuardedBy("mResourcesManager") final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>(); @GuardedBy("mResourcesManager") - @UnsupportedAppUsage + @UnsupportedAppUsage(trackingBug = 176961850, maxTargetSdk = Build.VERSION_CODES.R, + publicAlternatives = "Use {@code Context#getResources()#getConfiguration()} instead.") Configuration mPendingConfiguration = null; // An executor that performs multi-step transactions. private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this); @@ -516,6 +519,9 @@ public final class ActivityThread extends ClientTransactionHandler { private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null; + /** A client side controller to handle process level configuration changes. */ + private ConfigurationController mConfigurationController; + /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */ public static final class ActivityClientRecord { @UnsupportedAppUsage @@ -2058,7 +2064,7 @@ public final class ActivityThread extends ClientTransactionHandler { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case CONFIGURATION_CHANGED: - handleConfigurationChanged((Configuration) msg.obj); + mConfigurationController.handleConfigurationChanged((Configuration) msg.obj); break; case CLEAN_UP_CONTEXT: ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; @@ -2539,6 +2545,7 @@ public final class ActivityThread extends ClientTransactionHandler { return mExecutor; } + @Override @UnsupportedAppUsage public Application getApplication() { return mInitialApplication; @@ -2549,6 +2556,7 @@ public final class ActivityThread extends ClientTransactionHandler { return mBoundApplication.processName; } + @Override @UnsupportedAppUsage public ContextImpl getSystemContext() { synchronized (this) { @@ -2559,6 +2567,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } + @Override public ContextImpl getSystemUiContext() { synchronized (this) { if (mSystemUiContext == null) { @@ -3217,15 +3226,16 @@ public final class ActivityThread extends ClientTransactionHandler { @VisibleForTesting(visibility = PACKAGE) public Configuration getConfiguration() { - return mConfiguration; + return mConfigurationController.getConfiguration(); } @Override public void updatePendingConfiguration(Configuration config) { - synchronized (mResourcesManager) { - if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) { - mPendingConfiguration = config; - } + final Configuration updatedConfig = + mConfigurationController.updatePendingConfiguration(config); + // This is only done to maintain @UnsupportedAppUsage and should be removed someday. + if (updatedConfig != null) { + mPendingConfiguration = updatedConfig; } } @@ -3233,6 +3243,7 @@ public final class ActivityThread extends ClientTransactionHandler { * Returns {@code true} if the {@link android.app.ActivityManager.ProcessState} of the current * process is cached. */ + @Override @VisibleForTesting public boolean isCachedProcessState() { synchronized (mAppThread) { @@ -3269,10 +3280,8 @@ public final class ActivityThread extends ClientTransactionHandler { // non-cached. Except the case where there is a launching activity because the // LaunchActivityItem will handle it. if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) { - final Configuration pendingConfig; - synchronized (mResourcesManager) { - pendingConfig = mPendingConfiguration; - } + final Configuration pendingConfig = + mConfigurationController.getPendingConfiguration(false /* clearPending */); if (pendingConfig == null) { return; } @@ -3498,7 +3507,8 @@ public final class ActivityThread extends ClientTransactionHandler { if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); - Configuration config = new Configuration(mCompatConfiguration); + Configuration config = + new Configuration(mConfigurationController.getCompatConfiguration()); if (r.overrideConfig != null) { config.updateFrom(r.overrideConfig); } @@ -3711,7 +3721,7 @@ public final class ActivityThread extends ClientTransactionHandler { } // Make sure we are running with the most recent config. - handleConfigurationChanged(null, null); + mConfigurationController.handleConfigurationChanged(null, null); if (localLOGV) Slog.v( TAG, "Handling launch of " + r); @@ -3729,7 +3739,7 @@ public final class ActivityThread extends ClientTransactionHandler { final Activity a = performLaunchActivity(r, customIntent); if (a != null) { - r.createdConfig = new Configuration(mConfiguration); + r.createdConfig = new Configuration(mConfigurationController.getConfiguration()); reportSizeConfigurations(r); if (!r.activity.mFinished && pendingActions != null) { pendingActions.setOldState(r.state); @@ -4962,10 +4972,10 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_PAUSE); } - // TODO(b/127877792): Make LocalActivityManager call performStopActivityInner. We cannot remove + // TODO(b/176961850): Make LocalActivityManager call performStopActivityInner. We cannot remove // this since it's a high usage hidden API. /** Called from {@link LocalActivityManager}. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 127877792, + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 176961850, publicAlternatives = "{@code N/A}") final void performStopActivity(IBinder token, boolean saveState, String reason) { ActivityClientRecord r = mActivities.get(token); @@ -5193,8 +5203,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (apk != null) { apk.setCompatibilityInfo(data.info); } - handleConfigurationChanged(mConfiguration, data.info); - WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); + mConfigurationController.handleConfigurationChanged(data.info); } private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) { @@ -5451,7 +5460,6 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); mSomeActivitiesChanged = true; - Configuration changedConfig = null; int configChanges = 0; // First: make sure we have the most recent configuration and most @@ -5480,20 +5488,20 @@ public final class ActivityThread extends ClientTransactionHandler { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity " + tmp.token + " with configChanges=0x" + Integer.toHexString(configChanges)); - - if (mPendingConfiguration != null) { - changedConfig = mPendingConfiguration; - mPendingConfiguration = null; - } } + Configuration changedConfig = mConfigurationController.getPendingConfiguration( + true /* clearPending */); + mPendingConfiguration = null; + if (tmp.createdConfig != null) { // If the activity manager is passing us its current config, // assume that is really what we want regardless of what we // may have pending. - if (mConfiguration == null - || (tmp.createdConfig.isOtherSeqNewer(mConfiguration) - && mConfiguration.diff(tmp.createdConfig) != 0)) { + final Configuration config = mConfigurationController.getConfiguration(); + if (config == null + || (tmp.createdConfig.isOtherSeqNewer(config) + && config.diff(tmp.createdConfig) != 0)) { if (changedConfig == null || tmp.createdConfig.isOtherSeqNewer(changedConfig)) { changedConfig = tmp.createdConfig; @@ -5506,9 +5514,12 @@ public final class ActivityThread extends ClientTransactionHandler { // If there was a pending configuration change, execute it first. if (changedConfig != null) { - mCurDefaultDisplayDpi = changedConfig.densityDpi; - updateDefaultDensity(); - handleConfigurationChanged(changedConfig, null); + mConfigurationController.updateDefaultDensity(changedConfig.densityDpi); + mConfigurationController.handleConfigurationChanged(changedConfig, null); + + // These are only done to maintain @UnsupportedAppUsage and should be removed someday. + mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi(); + mConfiguration = mConfigurationController.getConfiguration(); } ActivityClientRecord r = mActivities.get(tmp.token); @@ -5596,7 +5607,8 @@ public final class ActivityThread extends ClientTransactionHandler { // Initialize a relaunch request. final MergedConfiguration mergedConfiguration = new MergedConfiguration( - r.createdConfig != null ? r.createdConfig : mConfiguration, + r.createdConfig != null + ? r.createdConfig : mConfigurationController.getConfiguration(), r.overrideConfig); final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain( null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */, @@ -5672,7 +5684,8 @@ public final class ActivityThread extends ClientTransactionHandler { } } - ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities) { + @Override + public ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities) { ArrayList<ComponentCallbacks2> callbacks = new ArrayList<ComponentCallbacks2>(); @@ -5732,44 +5745,6 @@ public final class ActivityThread extends ClientTransactionHandler { } /** - * Creates a new Configuration only if override would modify base. Otherwise returns base. - * @param base The base configuration. - * @param override The update to apply to the base configuration. Can be null. - * @return A Configuration representing base with override applied. - */ - private static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base, - @Nullable Configuration override) { - if (override == null) { - return base; - } - Configuration newConfig = new Configuration(base); - newConfig.updateFrom(override); - return newConfig; - } - - /** - * Decides whether to update a component's configuration and whether to inform it. - * @param cb The component callback to notify of configuration change. - * @param newConfig The new configuration. - */ - private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) { - // ContextThemeWrappers may override the configuration for that context. We must check and - // apply any overrides defined. - Configuration contextThemeWrapperOverrideConfig = null; - if (cb instanceof ContextThemeWrapper) { - final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb; - contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration(); - } - - // Apply the ContextThemeWrapper override if necessary. - // NOTE: Make sure the configurations are not modified, as they are treated as immutable - // in many places. - final Configuration configToReport = createNewConfigAndUpdateIfNotNull( - newConfig, contextThemeWrapperOverrideConfig); - cb.onConfigurationChanged(configToReport); - } - - /** * Decides whether to update an Activity's configuration and whether to inform it. * @param activity The activity to notify of configuration change. * @param newConfig The new configuration. @@ -5887,128 +5862,15 @@ public final class ActivityThread extends ClientTransactionHandler { } } - final Configuration applyCompatConfiguration(int displayDensity) { - Configuration config = mConfiguration; - if (mCompatConfiguration == null) { - mCompatConfiguration = new Configuration(); - } - mCompatConfiguration.setTo(mConfiguration); - if (mResourcesManager.applyCompatConfigurationLocked(displayDensity, - mCompatConfiguration)) { - config = mCompatConfiguration; - } - return config; - } - @Override public void handleConfigurationChanged(Configuration config) { - if (isCachedProcessState()) { - updatePendingConfiguration(config); - // If the process is in a cached state, delay the handling until the process is no - // longer cached. - return; - } - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); - mCurDefaultDisplayDpi = config.densityDpi; - handleConfigurationChanged(config, null /* compat */); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } - - private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { - - int configDiff; - boolean equivalent; - - final Theme systemTheme = getSystemContext().getTheme(); - final Theme systemUiTheme = getSystemUiContext().getTheme(); - - synchronized (mResourcesManager) { - if (mPendingConfiguration != null) { - if (!mPendingConfiguration.isOtherSeqNewer(config)) { - config = mPendingConfiguration; - mCurDefaultDisplayDpi = config.densityDpi; - updateDefaultDensity(); - } - mPendingConfiguration = null; - } - - if (config == null) { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && mHasImeComponent) { - Log.w(TAG, "handleConfigurationChanged for IME app but config is null"); - } - return; - } - - // This flag tracks whether the new configuration is fundamentally equivalent to the - // existing configuration. This is necessary to determine whether non-activity callbacks - // should receive notice when the only changes are related to non-public fields. - // We do not gate calling {@link #performActivityConfigurationChanged} based on this - // flag as that method uses the same check on the activity config override as well. - equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); - - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " - + config); - - final Resources appResources = mInitialApplication.getResources(); - if (appResources.hasOverrideDisplayAdjustments()) { - // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments, - // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated - // configuration also needs to set to the adjustments for consistency. - appResources.getDisplayAdjustments().getConfiguration().updateFrom(config); - } - mResourcesManager.applyConfigurationToResourcesLocked(config, compat, - appResources.getDisplayAdjustments()); - updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), - mResourcesManager.getConfiguration().getLocales()); - - if (mConfiguration == null) { - mConfiguration = new Configuration(); - } - if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && mHasImeComponent) { - Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete " - + ", config=" + config - + ", mConfiguration=" + mConfiguration); - } - return; - } + mConfigurationController.handleConfigurationChanged(config); - configDiff = mConfiguration.updateFrom(config); - config = applyCompatConfiguration(mCurDefaultDisplayDpi); - HardwareRenderer.sendDeviceConfigurationForDebugging(config); - - if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { - systemTheme.rebase(); - } - - if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { - systemUiTheme.rebase(); - } - } - - final ArrayList<ComponentCallbacks2> callbacks = - collectComponentCallbacks(false /* includeActivities */); - - freeTextLayoutCachesIfNeeded(configDiff); - - if (callbacks != null) { - final int N = callbacks.size(); - for (int i=0; i<N; i++) { - ComponentCallbacks2 cb = callbacks.get(i); - if (!equivalent) { - performConfigurationChanged(cb, config); - } else { - // TODO (b/135719017): Temporary log for debugging IME service. - if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) { - Log.w(TAG, "performConfigurationChanged didn't callback to IME " - + ", configDiff=" + configDiff - + ", mConfiguration=" + mConfiguration); - } - } - } - } + // These are only done to maintain @UnsupportedAppUsage and should be removed someday. + mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi(); + mConfiguration = mConfigurationController.getConfiguration(); + mPendingConfiguration = mConfigurationController.getPendingConfiguration( + false /* clearPending */); } /** @@ -6093,25 +5955,15 @@ public final class ActivityThread extends ClientTransactionHandler { // so that we actually call through to all components. // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to // store configurations per-process. + final Configuration config = mConfigurationController.getConfiguration(); Configuration newConfig = new Configuration(); - newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1; - handleConfigurationChanged(newConfig, null); + newConfig.assetsSeq = (config != null ? config.assetsSeq : 0) + 1; + mConfigurationController.handleConfigurationChanged(newConfig, null /* compat */); // Preserve windows to avoid black flickers when overlays change. relaunchAllActivities(true /* preserveWindows */, "handleApplicationInfoChanged"); } - static void freeTextLayoutCachesIfNeeded(int configDiff) { - if (configDiff != 0) { - // Ask text layout engine to free its caches if there is a locale change - boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); - if (hasLocaleConfigChange) { - Canvas.freeTextLayoutCaches(); - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches"); - } - } - } - /** * Sets the supplied {@code overrideConfig} as pending for the {@code activityToken}. Calling * this method prevents any calls to @@ -6188,7 +6040,7 @@ public final class ActivityThread extends ClientTransactionHandler { + ", config=" + overrideConfig); } final Configuration reportedConfig = performConfigurationChangedForActivity(r, - mCompatConfiguration, + mConfigurationController.getCompatConfiguration(), movedToDifferentDisplay ? displayId : r.activity.getDisplayId()); // Notify the ViewRootImpl instance about configuration changes. It may have initiated this // update to make sure that resources are updated before updating itself. @@ -6483,16 +6335,6 @@ public final class ActivityThread extends ClientTransactionHandler { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } - private void updateDefaultDensity() { - final int densityDpi = mCurDefaultDisplayDpi; - if (!mDensityCompatMode - && densityDpi != Configuration.DENSITY_DPI_UNDEFINED - && densityDpi != DisplayMetrics.DENSITY_DEVICE) { - DisplayMetrics.DENSITY_DEVICE = densityDpi; - Bitmap.setDefaultDensity(densityDpi); - } - } - /** * Returns the correct library directory for the current ABI. * <p> @@ -6519,27 +6361,6 @@ public final class ActivityThread extends ClientTransactionHandler { return insInfo.nativeLibraryDir; } - /** - * The LocaleList set for the app's resources may have been shuffled so that the preferred - * Locale is at position 0. We must find the index of this preferred Locale in the - * original LocaleList. - */ - private void updateLocaleListFromAppContext(Context context, LocaleList newLocaleList) { - final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); - final int newLocaleListSize = newLocaleList.size(); - for (int i = 0; i < newLocaleListSize; i++) { - if (bestLocale.equals(newLocaleList.get(i))) { - LocaleList.setDefault(newLocaleList, i); - return; - } - } - - // The app may have overridden the LocaleList with its own Locale - // (not present in the available list). Push the chosen Locale - // to the front of the list. - LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList)); - } - @UnsupportedAppUsage private void handleBindApplication(AppBindData data) { // Register the UI Thread as a sensitive thread to the runtime. @@ -6560,8 +6381,9 @@ public final class ActivityThread extends ClientTransactionHandler { AppSpecializationHooks.handleCompatChangesBeforeBindingApplication(); mBoundApplication = data; - mConfiguration = new Configuration(data.config); - mCompatConfiguration = new Configuration(data.config); + mConfigurationController.setConfiguration(data.config); + mConfigurationController.setCompatConfiguration(data.config); + mConfiguration = mConfigurationController.getConfiguration(); mProfiler = new Profiler(); String agent = null; @@ -6641,7 +6463,7 @@ public final class ActivityThread extends ClientTransactionHandler { mCurDefaultDisplayDpi = data.config.densityDpi; // This calls mResourcesManager so keep it within the synchronized block. - applyCompatConfiguration(mCurDefaultDisplayDpi); + mConfigurationController.applyCompatConfiguration(); } data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); @@ -6658,7 +6480,7 @@ public final class ActivityThread extends ClientTransactionHandler { mDensityCompatMode = true; Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT); } - updateDefaultDensity(); + mConfigurationController.updateDefaultDensity(data.config.densityDpi); // mCoreSettings is only updated from the main thread, while this function is only called // from main thread as well, so no need to lock here. @@ -6735,8 +6557,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); - updateLocaleListFromAppContext(appContext, - mResourcesManager.getConfiguration().getLocales()); + mConfigurationController.updateLocaleListFromAppContext(appContext); // Initialize the default http proxy in this process. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies"); @@ -7611,6 +7432,7 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage private void attach(boolean system, long startSeq) { sCurrentActivityThread = this; + mConfigurationController = new ConfigurationController(this); mSystemThread = system; if (!system) { android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", @@ -7662,8 +7484,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - ViewRootImpl.ConfigChangedCallback configChangedCallback - = (Configuration globalConfig) -> { + ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> { synchronized (mResourcesManager) { // TODO (b/135719017): Temporary log for debugging IME service. if (Build.IS_DEBUGGABLE && mHasImeComponent) { @@ -7676,14 +7497,15 @@ public final class ActivityThread extends ClientTransactionHandler { if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig, null /* compat */, mInitialApplication.getResources().getDisplayAdjustments())) { - updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), - mResourcesManager.getConfiguration().getLocales()); + mConfigurationController.updateLocaleListFromAppContext( + mInitialApplication.getApplicationContext()); // This actually changed the resources! Tell everyone about it. - if (mPendingConfiguration == null - || mPendingConfiguration.isOtherSeqNewer(globalConfig)) { - mPendingConfiguration = globalConfig; + final Configuration updatedConfig = + mConfigurationController.updatePendingConfiguration(globalConfig); + if (updatedConfig != null) { sendMessage(H.CONFIGURATION_CHANGED, globalConfig); + mPendingConfiguration = updatedConfig; } } } @@ -8019,6 +7841,16 @@ public final class ActivityThread extends ClientTransactionHandler { return false; } + @Override + public boolean isInDensityCompatMode() { + return mDensityCompatMode; + } + + @Override + public boolean hasImeComponent() { + return mHasImeComponent; + } + // ------------------ Regular JNI ------------------------ private native void nPurgePendingResources(); private native void nDumpGraphicsInfo(FileDescriptor fd); diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java new file mode 100644 index 000000000000..d91933c0f817 --- /dev/null +++ b/core/java/android/app/ActivityThreadInternal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.ComponentCallbacks2; + +import java.util.ArrayList; + +/** + * ActivityThread internal interface. + * It is a subset of ActivityThread and used for communicating with + * {@link ConfigurationController}. + */ +interface ActivityThreadInternal { + ContextImpl getSystemContext(); + + ContextImpl getSystemUiContext(); + + boolean isInDensityCompatMode(); + + boolean hasImeComponent(); + + boolean isCachedProcessState(); + + Application getApplication(); + + ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeActivities); +} diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java new file mode 100644 index 000000000000..0dbe3ba1c4fb --- /dev/null +++ b/core/java/android/app/ConfigurationController.java @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.app.ActivityThread.DEBUG_CONFIGURATION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.HardwareRenderer; +import android.inputmethodservice.InputMethodService; +import android.os.Build; +import android.os.LocaleList; +import android.os.Trace; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.Slog; +import android.view.ContextThemeWrapper; +import android.view.WindowManagerGlobal; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * A client side controller to handle process level configuration changes. + * @hide + */ +class ConfigurationController { + private static final String TAG = "ConfigurationController"; + + private final ActivityThreadInternal mActivityThread; + + private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); + + @GuardedBy("mResourcesManager") + private @Nullable Configuration mPendingConfiguration; + private @Nullable Configuration mCompatConfiguration; + private @Nullable Configuration mConfiguration; + + ConfigurationController(@NonNull ActivityThreadInternal activityThread) { + mActivityThread = activityThread; + } + + /** Update the pending configuration. */ + Configuration updatePendingConfiguration(@NonNull Configuration config) { + synchronized (mResourcesManager) { + if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) { + mPendingConfiguration = config; + return mPendingConfiguration; + } + } + return null; + } + + /** Get the pending configuration. */ + Configuration getPendingConfiguration(boolean clearPending) { + Configuration outConfig = null; + synchronized (mResourcesManager) { + if (mPendingConfiguration != null) { + outConfig = mPendingConfiguration; + if (clearPending) { + mPendingConfiguration = null; + } + } + } + return outConfig; + } + + /** Set the compatibility configuration. */ + void setCompatConfiguration(@NonNull Configuration config) { + mCompatConfiguration = new Configuration(config); + } + + /** Get the compatibility configuration. */ + Configuration getCompatConfiguration() { + return mCompatConfiguration; + } + + /** Apply the global compatibility configuration. */ + final Configuration applyCompatConfiguration() { + Configuration config = mConfiguration; + final int displayDensity = config.densityDpi; + if (mCompatConfiguration == null) { + mCompatConfiguration = new Configuration(); + } + mCompatConfiguration.setTo(mConfiguration); + if (mResourcesManager.applyCompatConfigurationLocked(displayDensity, + mCompatConfiguration)) { + config = mCompatConfiguration; + } + return config; + } + + /** Set the configuration. */ + void setConfiguration(@NonNull Configuration config) { + mConfiguration = new Configuration(config); + } + + /** Get current configuration. */ + Configuration getConfiguration() { + return mConfiguration; + } + + /** + * Update the configuration to latest. + * @param config The new configuration. + */ + void handleConfigurationChanged(@NonNull Configuration config) { + if (mActivityThread.isCachedProcessState()) { + updatePendingConfiguration(config); + // If the process is in a cached state, delay the handling until the process is no + // longer cached. + return; + } + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); + handleConfigurationChanged(config, null /* compat */); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + /** + * Update the configuration to latest. + * @param compat The new compatibility information. + */ + void handleConfigurationChanged(@NonNull CompatibilityInfo compat) { + handleConfigurationChanged(mConfiguration, compat); + WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration); + } + + /** + * Update the configuration to latest. + * @param config The new configuration. + * @param compat The new compatibility information. + */ + void handleConfigurationChanged(@Nullable Configuration config, + @Nullable CompatibilityInfo compat) { + int configDiff; + boolean equivalent; + + final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme(); + final Resources.Theme systemUiTheme = mActivityThread.getSystemUiContext().getTheme(); + + synchronized (mResourcesManager) { + if (mPendingConfiguration != null) { + if (!mPendingConfiguration.isOtherSeqNewer(config)) { + config = mPendingConfiguration; + updateDefaultDensity(config.densityDpi); + } + mPendingConfiguration = null; + } + + final boolean hasIme = mActivityThread.hasImeComponent(); + if (config == null) { + // TODO (b/135719017): Temporary log for debugging IME service. + if (Build.IS_DEBUGGABLE && hasIme) { + Log.w(TAG, "handleConfigurationChanged for IME app but config is null"); + } + return; + } + + // This flag tracks whether the new configuration is fundamentally equivalent to the + // existing configuration. This is necessary to determine whether non-activity callbacks + // should receive notice when the only changes are related to non-public fields. + // We do not gate calling {@link #performActivityConfigurationChanged} based on this + // flag as that method uses the same check on the activity config override as well. + equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config)); + + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Handle configuration changed: " + config); + } + + final Application app = mActivityThread.getApplication(); + final Resources appResources = app.getResources(); + if (appResources.hasOverrideDisplayAdjustments()) { + // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments, + // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated + // configuration also needs to set to the adjustments for consistency. + appResources.getDisplayAdjustments().getConfiguration().updateFrom(config); + } + mResourcesManager.applyConfigurationToResourcesLocked(config, compat, + appResources.getDisplayAdjustments()); + updateLocaleListFromAppContext(app.getApplicationContext()); + + if (mConfiguration == null) { + mConfiguration = new Configuration(); + } + if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { + // TODO (b/135719017): Temporary log for debugging IME service. + if (Build.IS_DEBUGGABLE && hasIme) { + Log.w(TAG, "handleConfigurationChanged for IME app but config seq is obsolete " + + ", config=" + config + + ", mConfiguration=" + mConfiguration); + } + return; + } + + configDiff = mConfiguration.updateFrom(config); + config = applyCompatConfiguration(); + HardwareRenderer.sendDeviceConfigurationForDebugging(config); + + if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { + systemTheme.rebase(); + } + + if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) { + systemUiTheme.rebase(); + } + } + + final ArrayList<ComponentCallbacks2> callbacks = + mActivityThread.collectComponentCallbacks(false /* includeActivities */); + + freeTextLayoutCachesIfNeeded(configDiff); + + if (callbacks != null) { + final int size = callbacks.size(); + for (int i = 0; i < size; i++) { + ComponentCallbacks2 cb = callbacks.get(i); + if (!equivalent) { + performConfigurationChanged(cb, config); + } else { + // TODO (b/135719017): Temporary log for debugging IME service. + if (Build.IS_DEBUGGABLE && cb instanceof InputMethodService) { + Log.w(TAG, "performConfigurationChanged didn't callback to IME " + + ", configDiff=" + configDiff + + ", mConfiguration=" + mConfiguration); + } + } + } + } + } + + /** + * Decides whether to update a component's configuration and whether to inform it. + * @param cb The component callback to notify of configuration change. + * @param newConfig The new configuration. + */ + void performConfigurationChanged(@NonNull ComponentCallbacks2 cb, + @NonNull Configuration newConfig) { + // ContextThemeWrappers may override the configuration for that context. We must check and + // apply any overrides defined. + Configuration contextThemeWrapperOverrideConfig = null; + if (cb instanceof ContextThemeWrapper) { + final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb; + contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration(); + } + + // Apply the ContextThemeWrapper override if necessary. + // NOTE: Make sure the configurations are not modified, as they are treated as immutable + // in many places. + final Configuration configToReport = createNewConfigAndUpdateIfNotNull( + newConfig, contextThemeWrapperOverrideConfig); + cb.onConfigurationChanged(configToReport); + } + + /** Update default density. */ + void updateDefaultDensity(int densityDpi) { + if (!mActivityThread.isInDensityCompatMode() + && densityDpi != Configuration.DENSITY_DPI_UNDEFINED + && densityDpi != DisplayMetrics.DENSITY_DEVICE) { + DisplayMetrics.DENSITY_DEVICE = densityDpi; + Bitmap.setDefaultDensity(densityDpi); + } + } + + /** Get current default display dpi. This is only done to maintain @UnsupportedAppUsage. */ + int getCurDefaultDisplayDpi() { + return mConfiguration.densityDpi; + } + + /** + * The LocaleList set for the app's resources may have been shuffled so that the preferred + * Locale is at position 0. We must find the index of this preferred Locale in the + * original LocaleList. + */ + void updateLocaleListFromAppContext(@NonNull Context context) { + final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0); + final LocaleList newLocaleList = mResourcesManager.getConfiguration().getLocales(); + final int newLocaleListSize = newLocaleList.size(); + for (int i = 0; i < newLocaleListSize; i++) { + if (bestLocale.equals(newLocaleList.get(i))) { + LocaleList.setDefault(newLocaleList, i); + return; + } + } + + // The app may have overridden the LocaleList with its own Locale + // (not present in the available list). Push the chosen Locale + // to the front of the list. + LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList)); + } + + /** + * Creates a new Configuration only if override would modify base. Otherwise returns base. + * @param base The base configuration. + * @param override The update to apply to the base configuration. Can be null. + * @return A Configuration representing base with override applied. + */ + static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base, + @Nullable Configuration override) { + if (override == null) { + return base; + } + Configuration newConfig = new Configuration(base); + newConfig.updateFrom(override); + return newConfig; + } + + /** Ask test layout engine to free its caches if there is a locale change. */ + static void freeTextLayoutCachesIfNeeded(int configDiff) { + if (configDiff != 0) { + boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0); + if (hasLocaleConfigChange) { + Canvas.freeTextLayoutCaches(); + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Cleared TextLayout Caches"); + } + } + } + } +} |