diff options
13 files changed, 185 insertions, 7 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt index a308c8ee38ca..3f4d3f8ba12a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/data/repository/ConfigurationRepositoryImplTest.kt @@ -98,6 +98,21 @@ class ConfigurationRepositoryImplTest : SysuiTestCase() { } @Test + fun onMovedToDisplays_updatesOnMovedToDisplay() = + testScope.runTest { + val lastOnMovedToDisplay by collectLastValue(underTest.onMovedToDisplay) + assertThat(lastOnMovedToDisplay).isNull() + + val configurationCallback = withArgCaptor { + verify(configurationController).addCallback(capture()) + } + + configurationCallback.onMovedToDisplay(1, Configuration()) + runCurrent() + assertThat(lastOnMovedToDisplay).isEqualTo(1) + } + + @Test fun onAnyConfigurationChange_updatesOnConfigChanged() = testScope.runTest { val lastAnyConfigurationChange by collectLastValue(underTest.onAnyConfigurationChange) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 5d1ce7c5ca05..929537dcf757 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -253,6 +253,16 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { verify(configurationForwarder).onConfigurationChanged(eq(config)) } + @Test + @EnableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND) + fun onMovedToDisplay_configForwarderSet_propagatesConfig() { + val config = Configuration() + + underTest.onMovedToDisplay(1, config) + + verify(configurationForwarder).dispatchOnMovedToDisplay(eq(1), eq(config)) + } + private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index 942ea65ec49e..e87077db8e75 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -21,11 +21,13 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_TYPE_CAR import android.os.LocaleList +import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener import com.google.common.truth.Truth.assertThat +import java.util.Locale import org.junit.Before import org.junit.Ignore import org.junit.Test @@ -34,7 +36,6 @@ import org.mockito.Mockito.doAnswer import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import java.util.Locale @RunWith(AndroidJUnit4::class) @SmallTest @@ -64,9 +65,11 @@ class ConfigurationControllerImplTest : SysuiTestCase() { mConfigurationController.addCallback(listener2) doAnswer { - mConfigurationController.removeCallback(listener2) - null - }.`when`(listener).onThemeChanged() + mConfigurationController.removeCallback(listener2) + null + } + .`when`(listener) + .onThemeChanged() mConfigurationController.notifyThemeChanged() verify(listener).onThemeChanged() @@ -208,7 +211,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() { assertThat(listener.maxBoundsChanged).isTrue() } - @Test fun localeListChanged_listenerNotified() { val config = mContext.resources.configuration @@ -289,7 +291,6 @@ class ConfigurationControllerImplTest : SysuiTestCase() { assertThat(listener.orientationChanged).isTrue() } - @Test fun multipleUpdates_listenerNotifiedOfAll() { val config = mContext.resources.configuration @@ -313,6 +314,17 @@ class ConfigurationControllerImplTest : SysuiTestCase() { } @Test + fun onMovedToDisplay_dispatchedToChildren() { + val config = mContext.resources.configuration + val listener = createAndAddListener() + + mConfigurationController.dispatchOnMovedToDisplay(newDisplayId = 1, config) + + assertThat(listener.display).isEqualTo(1) + assertThat(listener.changedConfig).isEqualTo(config) + } + + @Test @Ignore("b/261408895") fun equivalentConfigObject_listenerNotNotified() { val config = mContext.resources.configuration @@ -343,35 +355,49 @@ class ConfigurationControllerImplTest : SysuiTestCase() { var localeListChanged = false var layoutDirectionChanged = false var orientationChanged = false + var display = Display.DEFAULT_DISPLAY override fun onConfigChanged(newConfig: Configuration?) { changedConfig = newConfig } + override fun onDensityOrFontScaleChanged() { densityOrFontScaleChanged = true } + override fun onSmallestScreenWidthChanged() { smallestScreenWidthChanged = true } + override fun onMaxBoundsChanged() { maxBoundsChanged = true } + override fun onUiModeChanged() { uiModeChanged = true } + override fun onThemeChanged() { themeChanged = true } + override fun onLocaleListChanged() { localeListChanged = true } + override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) { layoutDirectionChanged = true } + override fun onOrientationChanged(orientation: Int) { orientationChanged = true } + override fun onMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration?) { + display = newDisplayId + changedConfig = newConfiguration + } + fun assertNoMethodsCalled() { assertThat(densityOrFontScaleChanged).isFalse() assertThat(smallestScreenWidthChanged).isFalse() @@ -391,6 +417,7 @@ class ConfigurationControllerImplTest : SysuiTestCase() { themeChanged = false localeListChanged = false layoutDirectionChanged = false + display = Display.DEFAULT_DISPLAY } } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt index 4d804d06fe87..747a2a9bd887 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt @@ -53,8 +53,12 @@ interface ConfigurationRepository { val onConfigurationChange: Flow<Unit> val scaleForResolution: Flow<Float> + val configurationValues: Flow<Configuration> + /** Emits the latest display this configuration controller has been moved to. */ + val onMovedToDisplay: Flow<Int> + fun getResolutionScale(): Float /** Convenience to context.resources.getDimensionPixelSize() */ @@ -117,6 +121,20 @@ constructor( configurationController.addCallback(callback) awaitClose { configurationController.removeCallback(callback) } } + override val onMovedToDisplay: Flow<Int> + get() = conflatedCallbackFlow { + val callback = + object : ConfigurationController.ConfigurationListener { + override fun onMovedToDisplay( + newDisplayId: Int, + newConfiguration: Configuration?, + ) { + trySend(newDisplayId) + } + } + configurationController.addCallback(callback) + awaitClose { configurationController.removeCallback(callback) } + } override val scaleForResolution: StateFlow<Float> = onConfigurationChange diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index bf672be3c8d0..0a9aa9b49e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -169,6 +169,10 @@ public class NotificationShadeWindowView extends WindowRootView { public void onMovedToDisplay(int displayId, Configuration config) { super.onMovedToDisplay(displayId, config); ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); + ShadeTraceLogger.INSTANCE.logOnMovedToDisplay(displayId, config); + if (mConfigurationForwarder != null) { + mConfigurationForwarder.dispatchOnMovedToDisplay(displayId, config); + } // When the window is moved we're only receiving a call to this method instead of the // onConfigurationChange itself. Let's just trigegr a normal config change. onConfigurationChanged(config); @@ -177,6 +181,7 @@ public class NotificationShadeWindowView extends WindowRootView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + ShadeTraceLogger.INSTANCE.logOnConfigChanged(newConfig); if (mConfigurationForwarder != null) { ShadeWindowGoesAround.isUnexpectedlyInLegacyMode(); mConfigurationForwarder.onConfigurationChanged(newConfig); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt new file mode 100644 index 000000000000..a596d4f64638 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeTraceLogger.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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 com.android.systemui.shade + +import android.content.res.Configuration +import android.os.Trace +import com.android.app.tracing.TraceUtils.traceAsync + +/** + * Centralized logging for shade-related events to a dedicated Perfetto track. + * + * Used by shade components to log events to a track named [TAG]. This consolidates shade-specific + * events into a single track for easier analysis in Perfetto, rather than scattering them across + * various threads' logs. + */ +object ShadeTraceLogger { + private const val TAG = "ShadeTraceLogger" + + fun logOnMovedToDisplay(displayId: Int, config: Configuration) { + if (!Trace.isEnabled()) return + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + TAG, + "onMovedToDisplay(displayId=$displayId, dpi=" + config.densityDpi + ")", + ) + } + + fun logOnConfigChanged(config: Configuration) { + if (!Trace.isEnabled()) return + Trace.instantForTrack( + Trace.TRACE_TAG_APP, + TAG, + "onConfigurationChanged(dpi=" + config.densityDpi + ")", + ) + } + + fun logMoveShadeWindowTo(displayId: Int) { + if (!Trace.isEnabled()) return + Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "moveShadeWindowTo(displayId=$displayId)") + } + + fun traceReparenting(r: () -> Unit) { + traceAsync(TAG, { "reparenting" }) { r() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt index 08c03e28d596..8d536accaf76 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt @@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo +import com.android.systemui.shade.ShadeTraceLogger.traceReparenting import com.android.systemui.shade.data.repository.ShadeDisplaysRepository import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround import com.android.systemui.util.kotlin.getOrNull @@ -68,6 +70,7 @@ constructor( /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */ private suspend fun moveShadeWindowTo(destinationId: Int) { Log.d(TAG, "Trying to move shade window to display with id $destinationId") + logMoveShadeWindowTo(destinationId) // Why using the shade context here instead of the view's Display? // The context's display is updated before the view one, so it is a better indicator of // which display the shade is supposed to be at. The View display is updated after the first @@ -83,7 +86,9 @@ constructor( return } try { - withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) } + withContext(mainThreadContext) { + traceReparenting { reparentToDisplayId(id = destinationId) } + } } catch (e: IllegalStateException) { Log.e( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index 858cac111525..9c7af181284e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -65,6 +65,13 @@ constructor(@Assisted private val context: Context) : listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() } } + override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) { + val listeners = synchronized(this.listeners) { ArrayList(this.listeners) } + listeners.filterForEach({ this.listeners.contains(it) }) { + it.onMovedToDisplay(newDisplayId, newConfiguration) + } + } + override fun onConfigurationChanged(newConfig: Configuration) { // Avoid concurrent modification exception val listeners = synchronized(this.listeners) { ArrayList(this.listeners) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt index 3fd46fc484a9..537e3e1893b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationForwarder.kt @@ -28,4 +28,13 @@ import android.content.res.Configuration interface ConfigurationForwarder { /** Should be called when a new configuration is received. */ fun onConfigurationChanged(newConfiguration: Configuration) + + /** + * Should be called when the view associated to this configuration forwarded moved to another + * display, usually as a consequence of [View.onMovedToDisplay]. + * + * For the default configuration forwarder (associated with the global configuration) this is + * never expected to be called. + */ + fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java index 1bb4e8c66ef1..c77f6c1b8552 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java @@ -45,5 +45,6 @@ public interface ConfigurationController extends CallbackController<Configuratio default void onLocaleListChanged() {} default void onLayoutDirectionChanged(boolean isLayoutRtl) {} default void onOrientationChanged(int orientation) {} + default void onMovedToDisplay(int newDisplayId, Configuration newConfiguration) {} } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 4d74254cf9f8..487049740079 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.ui.data.repository import android.content.res.Configuration +import android.view.Display import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module @@ -25,6 +26,7 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow @@ -46,6 +48,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor override val configurationValues: Flow<Configuration> = _configurationChangeValues.asSharedFlow() + private val _onMovedToDisplay = MutableStateFlow<Int>(Display.DEFAULT_DISPLAY) + override val onMovedToDisplay: StateFlow<Int> + get() = _onMovedToDisplay + private val _scaleForResolution = MutableStateFlow(1f) override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow() @@ -64,6 +70,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor onAnyConfigurationChange() } + fun onMovedToDisplay(newDisplayId: Int) { + _onMovedToDisplay.value = newDisplayId + } + fun setScaleForResolution(scale: Float) { _scaleForResolution.value = scale } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt index 32191277c94a..13673d16bf3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeConfigurationController.kt @@ -27,6 +27,10 @@ class FakeConfigurationController @Inject constructor() : listeners.forEach { it.onConfigChanged(newConfiguration) } } + override fun dispatchOnMovedToDisplay(newDisplayId: Int, newConfiguration: Configuration) { + listeners.forEach { it.onMovedToDisplay(newDisplayId, newConfiguration) } + } + override fun notifyThemeChanged() { listeners.forEach { it.onThemeChanged() } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java index 111c40d49efc..9cf25e8df727 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeConfigurationController.java @@ -16,6 +16,8 @@ package com.android.systemui.utils.leaks; import android.content.res.Configuration; +import androidx.annotation.NonNull; + import com.android.systemui.statusbar.policy.ConfigurationController; public class FakeConfigurationController @@ -43,4 +45,10 @@ public class FakeConfigurationController public String getNightModeName() { return "undefined"; } + + @Override + public void dispatchOnMovedToDisplay(int newDisplayId, + @NonNull Configuration newConfiguration) { + + } } |