summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2025-02-11 01:15:07 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-11 01:15:07 -0800
commit1b7405604b2278514e6eb56bac03ffd485b5f952 (patch)
tree4ef4be6ff599f4a3f29ade8a83d67d083daa5920
parent30d94b9405e762d41e2dd9f66d387d4dd587e08c (diff)
parentf23ceff6223d7d6dba6b194f4309d219bebe41a3 (diff)
Merge "Consolidate configuraton dispatching logic" into main
-rw-r--r--core/java/android/window/ConfigurationDispatcher.java44
-rw-r--r--core/java/android/window/WindowContext.java13
-rw-r--r--core/java/android/window/WindowContextController.java1
-rw-r--r--core/java/android/window/WindowProviderService.java14
-rw-r--r--core/java/android/window/WindowTokenClient.java24
-rw-r--r--core/tests/coretests/src/android/window/ConfigurationDispatcherTest.kt110
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