diff options
| author | 2025-02-11 01:15:07 -0800 | |
|---|---|---|
| committer | 2025-02-11 01:15:07 -0800 | |
| commit | 1b7405604b2278514e6eb56bac03ffd485b5f952 (patch) | |
| tree | 4ef4be6ff599f4a3f29ade8a83d67d083daa5920 | |
| parent | 30d94b9405e762d41e2dd9f66d387d4dd587e08c (diff) | |
| parent | f23ceff6223d7d6dba6b194f4309d219bebe41a3 (diff) | |
Merge "Consolidate configuraton dispatching logic" into main
6 files changed, 186 insertions, 20 deletions
diff --git a/core/java/android/window/ConfigurationDispatcher.java b/core/java/android/window/ConfigurationDispatcher.java new file mode 100644 index 000000000000..b8f0da1e2c58 --- /dev/null +++ b/core/java/android/window/ConfigurationDispatcher.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 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.window; + +import android.annotation.NonNull; +import android.content.ComponentCallbacks; +import android.content.res.Configuration; + +/** + * Indicates a {@link android.content.Context} could propagate the + * {@link android.content.res.Configuration} from the server side and users may listen to the + * updates through {@link android.content.Context#registerComponentCallbacks(ComponentCallbacks)}. + * + * @hide + */ +public interface ConfigurationDispatcher { + + /** + * Called when there's configuration update from the server side. + */ + void dispatchConfigurationChanged(@NonNull Configuration configuration); + + /** + * Indicates that if this dispatcher should report the change even if it's not + * {@link Configuration#diffPublicOnly}. + */ + default boolean shouldReportPrivateChanges() { + return false; + } +} diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java index 84a8b8f5b5b0..778ccafedd3c 100644 --- a/core/java/android/window/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -17,8 +17,6 @@ package android.window; import static android.view.WindowManagerImpl.createWindowContextWindowManager; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; @@ -46,7 +44,8 @@ import java.lang.ref.Reference; * @hide */ @UiContext -public class WindowContext extends ContextWrapper implements WindowProvider { +public class WindowContext extends ContextWrapper implements WindowProvider, + ConfigurationDispatcher { private final WindowManager mWindowManager; @WindowManager.LayoutParams.WindowType private final int mType; @@ -155,7 +154,7 @@ public class WindowContext extends ContextWrapper implements WindowProvider { } /** Dispatch {@link Configuration} to each {@link ComponentCallbacks}. */ - @VisibleForTesting(visibility = PACKAGE) + @Override public void dispatchConfigurationChanged(@NonNull Configuration newConfig) { mCallbacksController.dispatchConfigurationChanged(newConfig); } @@ -170,4 +169,10 @@ public class WindowContext extends ContextWrapper implements WindowProvider { public Bundle getWindowContextOptions() { return mOptions; } + + @Override + public boolean shouldReportPrivateChanges() { + // Always dispatch config changes to WindowContext. + return true; + } } diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 1e2f454adeef..d31e43f6102d 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -86,7 +86,6 @@ public class WindowContextController { * @param token The token used to attach to a window manager node. It is usually from * {@link Context#getWindowContextToken()}. */ - @VisibleForTesting public WindowContextController(@NonNull WindowTokenClient token) { mToken = token; } diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index c81c9eccfa3d..8468867de033 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -49,9 +49,11 @@ import android.view.WindowManagerImpl; * * @hide */ +@SuppressWarnings("HiddenSuperclass") @TestApi @UiContext -public abstract class WindowProviderService extends Service implements WindowProvider { +public abstract class WindowProviderService extends Service implements WindowProvider, + ConfigurationDispatcher { private static final String TAG = WindowProviderService.class.getSimpleName(); @@ -240,4 +242,14 @@ public abstract class WindowProviderService extends Service implements WindowPro mController.detachIfNeeded(); mCallbacksController.clearCallbacks(); } + + /** + * {@inheritDoc} + * + * @hide + */ + @Override + public void dispatchConfigurationChanged(@NonNull Configuration configuration) { + onConfigurationChanged(configuration); + } } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 5a544d3549a0..9b296c80d298 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -121,8 +121,6 @@ public class WindowTokenClient extends Binder { newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); } - // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService - // are inherited from WindowProvider. /** * Called when {@link Configuration} updates from the server side receive. * @@ -169,7 +167,7 @@ public class WindowTokenClient extends Binder { CompatibilityInfo.applyOverrideIfNeeded(newConfig); final boolean displayChanged; final boolean shouldUpdateResources; - final int diff; + final int publicDiff; final Configuration currentConfig; synchronized (mConfiguration) { @@ -177,7 +175,7 @@ public class WindowTokenClient extends Binder { shouldUpdateResources = shouldUpdateResources(this, mConfiguration, newConfig, newConfig /* overrideConfig */, displayChanged, null /* configChanged */); - diff = mConfiguration.diffPublicOnly(newConfig); + publicDiff = mConfiguration.diffPublicOnly(newConfig); currentConfig = mShouldDumpConfigForIme ? new Configuration(mConfiguration) : null; if (shouldUpdateResources) { mConfiguration.setTo(newConfig); @@ -200,17 +198,15 @@ public class WindowTokenClient extends Binder { // TODO(ag/9789103): update resource manager logic to track non-activity tokens mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); - if (shouldReportConfigChange && context instanceof WindowContext) { - final WindowContext windowContext = (WindowContext) context; - windowContext.dispatchConfigurationChanged(newConfig); + if (shouldReportConfigChange && context instanceof ConfigurationDispatcher dispatcher) { + // Updating resources implies some fields of configuration are updated despite they + // are public or not. + if (dispatcher.shouldReportPrivateChanges() || publicDiff != 0) { + dispatcher.dispatchConfigurationChanged(newConfig); + } } - if (shouldReportConfigChange && diff != 0 - && context instanceof WindowProviderService) { - final WindowProviderService windowProviderService = (WindowProviderService) context; - windowProviderService.onConfigurationChanged(newConfig); - } - freeTextLayoutCachesIfNeeded(diff); + freeTextLayoutCachesIfNeeded(publicDiff); if (mShouldDumpConfigForIme) { if (!shouldReportConfigChange) { Log.d(TAG, "Only apply configuration update to Resources because " @@ -219,7 +215,7 @@ public class WindowTokenClient extends Binder { + ", config=" + context.getResources().getConfiguration() + ", display ID=" + context.getDisplayId() + "\n" + Debug.getCallers(5)); - } else if (diff == 0) { + } else if (publicDiff == 0) { Log.d(TAG, "Configuration not dispatch to IME because configuration has no " + " public difference with updated config. " + " Current config=" + context.getResources().getConfiguration() diff --git a/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt new file mode 100644 index 000000000000..e8e8a2e335f2 --- /dev/null +++ b/core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2025 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.window + +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW +import android.content.ContextWrapper +import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.platform.test.annotations.Presubmit +import android.view.Display.DEFAULT_DISPLAY +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** + * Test to verify [ConfigurationDispatcher] + * + * Build/Install/Run: + * atest FrameworksCoreTests:ConfigurationDispatcherTest + */ +@SmallTest +@Presubmit +@RunWith(Parameterized::class) +class ConfigurationDispatcherTest(private val shouldReportPrivateChanges: Boolean) { + + /** + * Verifies [ConfigurationDispatcher.shouldReportPrivateChanges]. + */ + @Test + fun testConfigurationDispatcher() { + val receiver = TestConfigurationReceiver(shouldReportPrivateChanges) + val config = Configuration().apply { + orientation = ORIENTATION_PORTRAIT + } + + // Verify public config field change + receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true) + + assertThat(receiver.receivedConfig).isEqualTo(config) + + // Clear the config value + receiver.receivedConfig.unset() + + // Verify private config field change + config.windowConfiguration.windowingMode = WINDOWING_MODE_MULTI_WINDOW + + receiver.windowToken.onConfigurationChangedInner(receiver, config, DEFAULT_DISPLAY, true) + + assertThat(receiver.receivedConfig).isEqualTo( + if (shouldReportPrivateChanges) { + config + } else { + Configuration.EMPTY + } + ) + } + + /** + * Test [android.content.Context] to implement [ConfigurationDispatcher] for testing. + * + * @param shouldReportPrivateChanges used to override + * [ConfigurationDispatcher.shouldReportPrivateChanges] for testing, + */ + private class TestConfigurationReceiver( + private val shouldReportPrivateChanges: Boolean + ) : ContextWrapper(null), ConfigurationDispatcher { + val windowToken = WindowTokenClient() + val receivedConfig = Configuration() + + init { + windowToken.attachContext(this) + } + + override fun dispatchConfigurationChanged(configuration: Configuration) { + receivedConfig.setTo(configuration) + } + + override fun shouldReportPrivateChanges(): Boolean { + return shouldReportPrivateChanges + } + + override fun getDisplayId(): Int { + return DEFAULT_DISPLAY + } + } + + companion object { + @Parameterized.Parameters(name = "shouldReportPrivateChange={0}") + @JvmStatic + fun data(): Collection<Any> { + return listOf(true, false) + } + } +}
\ No newline at end of file |