diff options
10 files changed, 397 insertions, 369 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt index faaa4c415d28..1dd8ca9221a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt @@ -14,23 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.window +package com.android.systemui.display.data.repository -import android.platform.test.annotations.EnableFlags import android.view.Display -import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.app.viewcapture.ViewCaptureAwareWindowManager -import com.android.app.viewcapture.mockViewCaptureAwareWindowManager import com.android.systemui.SysuiTestCase -import com.android.systemui.display.data.repository.displayRepository -import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository -import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.unconfinedTestDispatcher -import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking @@ -43,28 +35,13 @@ import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest -@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) -class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { +class PerDisplayStoreImplTest : SysuiTestCase() { private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher } private val testScope = kosmos.testScope private val fakeDisplayRepository = kosmos.displayRepository - private val store = - MultiDisplayStatusBarWindowControllerStore( - backgroundApplicationScope = kosmos.applicationCoroutineScope, - controllerFactory = kosmos.fakeStatusBarWindowControllerFactory, - displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository, - viewCaptureAwareWindowManagerFactory = - object : ViewCaptureAwareWindowManager.Factory { - override fun create( - windowManager: WindowManager - ): ViewCaptureAwareWindowManager { - return kosmos.mockViewCaptureAwareWindowManager - } - }, - displayRepository = fakeDisplayRepository, - ) + private val store = kosmos.fakePerDisplayStore @Before fun start() { @@ -80,34 +57,52 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { @Test fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = testScope.runTest { - val controller = store.defaultDisplay + val instance = store.defaultDisplay - assertThat(store.defaultDisplay).isSameInstanceAs(controller) + assertThat(store.defaultDisplay).isSameInstanceAs(instance) } @Test fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(instance) } @Test fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(instance) } @Test(expected = IllegalArgumentException::class) fun forDisplay_nonExistingDisplayId_throws() = testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } + @Test + fun forDisplay_afterDisplayRemoved_onDisplayRemovalActionInvoked() = + testScope.runTest { + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.removalActions).containsExactly(instance) + } + + @Test + fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() = + testScope.runTest { + store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.removalActions).isEmpty() + } + private fun createDisplay(displayId: Int): Display = mock { on { getDisplayId() } doReturn displayId } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index c804fc6990ae..ba5fb7f8df00 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -98,7 +98,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) @@ -135,7 +135,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) @@ -164,8 +164,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val chipWidth = 30 val dotWidth = 10 val isRtl = false - val contentRect = - Rect(/* left = */ 0, /* top = */ 10, /* right = */ 1000, /* bottom = */ 100) + val contentRect = Rect(/* left= */ 0, /* top= */ 10, /* right= */ 1000, /* bottom= */ 100) val chipBounds = getPrivacyChipBoundingRectForInsets(contentRect, dotWidth, chipWidth, isRtl) @@ -207,7 +206,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -228,7 +227,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -251,7 +250,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -263,7 +262,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - dcBounds.height() - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -278,7 +277,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -320,7 +319,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -331,7 +330,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { protectionBounds.bottom, 0, screenBounds.height() - minRightPadding, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -346,7 +345,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -369,7 +368,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -381,7 +380,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - protectionBounds.bottom - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -396,7 +395,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -415,7 +414,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = screenBounds.right - dcWidth, top = 0, right = screenBounds.right, - bottom = dcHeight + bottom = dcHeight, ) val dcBoundsLandscape = Rect(left = 0, top = 0, right = dcHeight, bottom = dcWidth) val dcBoundsSeascape = @@ -423,14 +422,14 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = screenBounds.right - dcHeight, top = screenBounds.bottom - dcWidth, right = screenBounds.right - dcHeight, - bottom = screenBounds.bottom - dcWidth + bottom = screenBounds.bottom - dcWidth, ) val dcBoundsUpsideDown = Rect( left = 0, top = screenBounds.bottom - dcHeight, right = dcWidth, - bottom = screenBounds.bottom - dcHeight + bottom = screenBounds.bottom - dcHeight, ) val minLeftPadding = 20 val minRightPadding = 20 @@ -448,7 +447,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = dcBoundsPortrait.left - dotWidth, - bottom = sbHeightPortrait + bottom = sbHeightPortrait, ) var bounds = @@ -463,7 +462,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -475,7 +474,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = dcBoundsLandscape.height(), top = 0, right = screenBounds.height() - minRightPadding, - bottom = sbHeightLandscape + bottom = sbHeightLandscape, ) bounds = @@ -490,7 +489,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -502,7 +501,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = screenBounds.width() - minRightPadding, - bottom = sbHeightPortrait + bottom = sbHeightPortrait, ) bounds = @@ -517,7 +516,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -529,7 +528,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = screenBounds.height() - minRightPadding, - bottom = sbHeightLandscape + bottom = sbHeightLandscape, ) bounds = @@ -544,7 +543,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -584,7 +583,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -595,7 +594,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { protectionBounds.bottom, 0, screenBounds.height() - minRightPadding, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -610,7 +609,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -633,7 +632,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -645,7 +644,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - protectionBounds.bottom - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -660,7 +659,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -682,7 +681,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl = false, dotWidth = 10, bottomAlignedMargin = BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight = 15 + statusBarContentHeight = 15, ) assertThat(bounds.top).isEqualTo(0) @@ -704,7 +703,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl = false, dotWidth = 10, bottomAlignedMargin = 5, - statusBarContentHeight = 15 + statusBarContentHeight = 15, ) // Content in the status bar is centered vertically. To achieve the bottom margin we want, @@ -756,7 +755,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -777,7 +776,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -798,7 +797,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -809,7 +808,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - dcBounds.height() - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -824,7 +823,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -860,7 +859,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -880,7 +879,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -900,7 +899,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -920,7 +919,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @@ -958,7 +957,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -968,12 +967,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun testDisplayChanged_returnsUpdatedInsets() { // GIVEN: get insets on the first display and switch to the second display val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) @@ -993,12 +992,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // GIVEN: get insets on the first display, switch to the second display, // get insets and switch back val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) @@ -1024,12 +1023,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) configurationController.onConfigurationChanged(configuration) val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1053,12 +1052,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun onDensityOrFontScaleChanged_listenerNotified() { configuration.densityDpi = 12 val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1081,12 +1080,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @Test fun onThemeChanged_listenerNotified() { val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1108,13 +1107,13 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { expected: Rect, actual: Rect, @Rotation currentRotation: Int, - @Rotation targetRotation: Int + @Rotation targetRotation: Int, ) { assertTrue( "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" + " targetRotation=${RotationUtils.toString(targetRotation)}" + " expected=$expected actual=$actual", - expected.equals(actual) + expected.equals(actual), ) } @@ -1126,7 +1125,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left: Rect = Rect(), top: Rect = Rect(), right: Rect = Rect(), - bottom: Rect = Rect() + bottom: Rect = Rect(), ) { whenever(dc.boundingRects) .thenReturn(listOf(left, top, right, bottom).filter { !it.isEmpty }) diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt new file mode 100644 index 000000000000..2ce3e43389fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt @@ -0,0 +1,108 @@ +/* + * 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.display.data.repository + +import android.view.Display +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.qualifiers.Background +import java.io.PrintWriter +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** Provides per display instances of [T]. */ +interface PerDisplayStore<T> { + + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + val defaultDisplay: T + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + fun forDisplay(displayId: Int): T +} + +abstract class PerDisplayStoreImpl<T>( + @Background private val backgroundApplicationScope: CoroutineScope, + private val displayRepository: DisplayRepository, +) : PerDisplayStore<T>, CoreStartable { + + private val perDisplayInstances = ConcurrentHashMap<Int, T>() + + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + override val defaultDisplay: T + get() = forDisplay(Display.DEFAULT_DISPLAY) + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + override fun forDisplay(displayId: Int): T { + if (displayRepository.getDisplay(displayId) == null) { + throw IllegalArgumentException("Display with id $displayId doesn't exist.") + } + return perDisplayInstances.computeIfAbsent(displayId) { + createInstanceForDisplay(displayId) + } + } + + abstract fun createInstanceForDisplay(displayId: Int): T + + override fun start() { + val instanceType = instanceClass.simpleName + backgroundApplicationScope.launch(CoroutineName("PerDisplayStore#<$instanceType>start")) { + displayRepository.displayRemovalEvent.collect { removedDisplayId -> + val removedInstance = perDisplayInstances.remove(removedDisplayId) + removedInstance?.let { onDisplayRemovalAction(it) } + } + } + } + + abstract val instanceClass: Class<T> + + /** + * Will be called when the display associated with [instance] was removed. It allows to perform + * any clean up if needed. + */ + open suspend fun onDisplayRemovalAction(instance: T) {} + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println(perDisplayInstances) + } +} + +class SingleDisplayStore<T>(defaultInstance: T) : PerDisplayStore<T> { + override val defaultDisplay: T = defaultInstance + + override fun forDisplay(displayId: Int): T = defaultDisplay +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt index 6c3802676f26..041f0b0fdf93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt @@ -16,88 +16,52 @@ package com.android.systemui.statusbar.core -import android.view.Display -import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore import com.android.systemui.statusbar.window.StatusBarWindowControllerStore -import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** Provides per display instances of [StatusBarInitializer]. */ -interface StatusBarInitializerStore { - /** - * The instance for the default/main display of the device. For example, on a phone or a tablet, - * the default display is the internal/built-in display of the device. - * - * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. - */ - val defaultDisplay: StatusBarInitializer - - /** - * Returns an instance for a specific display id. - * - * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing - * displays. - */ - fun forDisplay(displayId: Int): StatusBarInitializer -} +interface StatusBarInitializerStore : PerDisplayStore<StatusBarInitializer> @SysUISingleton class MultiDisplayStatusBarInitializerStore @Inject constructor( - @Background private val backgroundApplicationScope: CoroutineScope, + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, private val factory: StatusBarInitializer.Factory, - private val displayRepository: DisplayRepository, private val statusBarWindowControllerStore: StatusBarWindowControllerStore, -) : StatusBarInitializerStore, CoreStartable { +) : + StatusBarInitializerStore, + PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) { init { StatusBarConnectedDisplays.assertInNewMode() } - private val perDisplayInitializers = ConcurrentHashMap<Int, StatusBarInitializer>() - - override val defaultDisplay: StatusBarInitializer - get() = forDisplay(Display.DEFAULT_DISPLAY) - - override fun forDisplay(displayId: Int): StatusBarInitializer { - if (displayRepository.getDisplay(displayId) == null) { - throw IllegalArgumentException("Display with id $displayId doesn't exist.") - } - return perDisplayInitializers.computeIfAbsent(displayId) { - factory.create( - statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId) - ) - } + override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer { + return factory.create( + statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId) + ) } - override fun start() { - backgroundApplicationScope.launch( - CoroutineName("MultiDisplayStatusBarInitializerStore#start") - ) { - displayRepository.displayRemovalEvent.collect { removedDisplayId -> - perDisplayInitializers.remove(removedDisplayId) - } - } - } + override val instanceClass = StatusBarInitializer::class.java } @SysUISingleton class SingleDisplayStatusBarInitializerStore @Inject -constructor(private val defaultInstance: StatusBarInitializer) : StatusBarInitializerStore { +constructor(defaultInitializer: StatusBarInitializer) : + StatusBarInitializerStore, + PerDisplayStore<StatusBarInitializer> by SingleDisplayStore(defaultInitializer) { init { StatusBarConnectedDisplays.assertInLegacyMode() } - - override val defaultDisplay: StatusBarInitializer = defaultInstance - - override fun forDisplay(displayId: Int): StatusBarInitializer = defaultInstance } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 5aad11fe1034..f6f4503b210a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -17,17 +17,20 @@ package com.android.systemui.statusbar.dagger import android.content.Context -import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable +import com.android.systemui.SysUICutoutProvider import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore @@ -108,5 +111,16 @@ abstract class StatusBarModule { fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("OngoingCall", 75) } + + @Provides + @SysUISingleton + fun contentInsetsProvider( + factory: StatusBarContentInsetsProviderImpl.Factory, + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProvider { + return factory.create(context, configurationController, sysUICutoutProvider) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 613efaa148f5..c6f6bd90fce6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -34,10 +34,10 @@ import com.android.systemui.Dumpable import com.android.systemui.StatusBarInsetsCommand import com.android.systemui.SysUICutoutInformation import com.android.systemui.SysUICutoutProvider -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl.CacheKey import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE @@ -47,9 +47,11 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation import com.android.systemui.util.leak.RotationUtils.getExactRotation import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import java.io.PrintWriter import java.lang.Math.max -import javax.inject.Inject /** * Encapsulates logic that can solve for the left/right insets required for the status bar contents. @@ -64,19 +66,87 @@ import javax.inject.Inject * * NOTE: This class is not threadsafe */ -@SysUISingleton -class StatusBarContentInsetsProvider -@Inject +interface StatusBarContentInsetsProvider : + CallbackController<StatusBarContentInsetsChangedListener> { + + /** + * Some views may need to care about whether or not the current top display cutout is located in + * the corner rather than somewhere in the center. In the case of a corner cutout, the status + * bar area is contiguous. + */ + fun currentRotationHasCornerCutout(): Boolean + + /** + * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy + * dot in the coordinates relative to the given rotation. + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from + * which this method is called) + */ + fun getBoundingRectForPrivacyChipForRotation( + @Rotation rotation: Int, + displayCutout: DisplayCutout?, + ): Rect + + /** + * Calculate the distance from the left, right and top edges of the screen to the status bar + * content area. This differs from the content area rects in that these values can be used + * directly as padding. + * + * @param rotation the target rotation for which to calculate insets + */ + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets + + /** + * Calculate the insets for the status bar content in the device's current rotation + * + * @see getStatusBarContentAreaForRotation + */ + fun getStatusBarContentInsetsForCurrentRotation(): Insets + + /** + * Calculates the area of the status bar contents invariant of the current device rotation, in + * the target rotation's coordinates + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from + * which this method is called) + */ + fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect + + /** Get the status bar content area for the given rotation, in absolute bounds */ + fun getStatusBarContentAreaForCurrentRotation(): Rect + + fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int + + interface Factory { + fun create( + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProvider + } +} + +class StatusBarContentInsetsProviderImpl +@AssistedInject constructor( - val context: Context, - val configurationController: ConfigurationController, + @Assisted val context: Context, + @Assisted val configurationController: ConfigurationController, val dumpManager: DumpManager, val commandRegistry: CommandRegistry, - val sysUICutoutProvider: SysUICutoutProvider, -) : - CallbackController<StatusBarContentInsetsChangedListener>, - ConfigurationController.ConfigurationListener, - Dumpable { + @Assisted val sysUICutoutProvider: SysUICutoutProvider, +) : StatusBarContentInsetsProvider, ConfigurationController.ConfigurationListener, Dumpable { + + @AssistedFactory + interface Factory : StatusBarContentInsetsProvider.Factory { + override fun create( + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProviderImpl + } // Limit cache size as potentially we may connect large number of displays // (e.g. network displays) @@ -95,7 +165,7 @@ constructor( object : StatusBarInsetsCommand.Callback { override fun onExecute( command: StatusBarInsetsCommand, - printWriter: PrintWriter + printWriter: PrintWriter, ) { executeCommand(command, printWriter) } @@ -133,12 +203,7 @@ constructor( listeners.forEach { it.onStatusBarContentInsetsChanged() } } - /** - * Some views may need to care about whether or not the current top display cutout is located in - * the corner rather than somewhere in the center. In the case of a corner cutout, the status - * bar area is contiguous. - */ - fun currentRotationHasCornerCutout(): Boolean { + override fun currentRotationHasCornerCutout(): Boolean { val cutout = checkNotNull(context.display).cutout ?: return false val topBounds = cutout.boundingRectTop @@ -148,17 +213,9 @@ constructor( return topBounds.left <= 0 || topBounds.right >= point.x } - /** - * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy - * dot in the coordinates relative to the given rotation. - * - * @param rotation the rotation for which the bounds are required. This is an absolute value - * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from - * which this method is called) - */ - fun getBoundingRectForPrivacyChipForRotation( + override fun getBoundingRectForPrivacyChipForRotation( @Rotation rotation: Int, - displayCutout: DisplayCutout? + displayCutout: DisplayCutout?, ): Rect { val key = getCacheKey(rotation, displayCutout) var insets = insetsCache[key] @@ -176,14 +233,7 @@ constructor( return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl) } - /** - * Calculate the distance from the left, right and top edges of the screen to the status bar - * content area. This differs from the content area rects in that these values can be used - * directly as padding. - * - * @param rotation the target rotation for which to calculate insets - */ - fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets = + override fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets = traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") { val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation() val displayCutout = sysUICutout?.cutout @@ -202,31 +252,17 @@ constructor( rotation, sysUICutout, getResourcesForRotation(rotation, context), - key + key, ) Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0) } - /** - * Calculate the insets for the status bar content in the device's current rotation - * - * @see getStatusBarContentAreaForRotation - */ - fun getStatusBarContentInsetsForCurrentRotation(): Insets { + override fun getStatusBarContentInsetsForCurrentRotation(): Insets { return getStatusBarContentInsetsForRotation(getExactRotation(context)) } - /** - * Calculates the area of the status bar contents invariant of the current device rotation, in - * the target rotation's coordinates - * - * @param rotation the rotation for which the bounds are required. This is an absolute value - * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from - * which this method is called) - */ - @JvmOverloads - fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect { + override fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect { val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation() val displayCutout = sysUICutout?.cutout val key = getCacheKey(rotation, displayCutout) @@ -235,12 +271,11 @@ constructor( rotation, sysUICutout, getResourcesForRotation(rotation, context), - key + key, ) } - /** Get the status bar content area for the given rotation, in absolute bounds */ - fun getStatusBarContentAreaForCurrentRotation(): Rect { + override fun getStatusBarContentAreaForCurrentRotation(): Rect { val rotation = getExactRotation(context) return getStatusBarContentAreaForRotation(rotation) } @@ -249,7 +284,7 @@ constructor( @Rotation targetRotation: Int, sysUICutout: SysUICutoutInformation?, rotatedResources: Resources, - key: CacheKey + key: CacheKey, ): Rect { return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources).also { insetsCache.put(key, it) @@ -259,7 +294,7 @@ constructor( private fun getCalculatedAreaForRotation( sysUICutout: SysUICutoutInformation?, @Rotation targetRotation: Int, - rotatedResources: Resources + rotatedResources: Resources, ): Rect { val currentRotation = getExactRotation(context) @@ -299,7 +334,7 @@ constructor( configurationController.isLayoutRtl, dotWidth, bottomAlignedMargin, - statusBarContentHeight + statusBarContentHeight, ) } @@ -349,7 +384,7 @@ constructor( return resources.getDimensionPixelSize(dimenRes) } - fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int { + override fun getStatusBarPaddingTop(@Rotation rotation: Int?): Int { val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources return res.getDimensionPixelSize(R.dimen.status_bar_padding_top) } @@ -364,13 +399,13 @@ constructor( CacheKey( rotation = rotation, displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds), - displayCutout = displayCutout + displayCutout = displayCutout, ) private data class CacheKey( @Rotation val rotation: Int, val displaySize: Rect, - val displayCutout: DisplayCutout? + val displayCutout: DisplayCutout?, ) } @@ -395,21 +430,21 @@ fun getPrivacyChipBoundingRectForInsets( contentRect: Rect, dotWidth: Int, chipWidth: Int, - isRtl: Boolean + isRtl: Boolean, ): Rect { return if (isRtl) { Rect( contentRect.left - dotWidth, contentRect.top, contentRect.left + chipWidth, - contentRect.bottom + contentRect.bottom, ) } else { Rect( contentRect.right - chipWidth, contentRect.top, contentRect.right + dotWidth, - contentRect.bottom + contentRect.bottom, ) } } @@ -443,7 +478,7 @@ fun calculateInsetsForRotationWithRotatedResources( isRtl: Boolean, dotWidth: Int, bottomAlignedMargin: Int, - statusBarContentHeight: Int + statusBarContentHeight: Int, ): Rect { /* TODO: Check if this is ever used for devices with no rounded corners @@ -467,7 +502,7 @@ fun calculateInsetsForRotationWithRotatedResources( targetRotation, currentRotation, bottomAlignedMargin, - statusBarContentHeight + statusBarContentHeight, ) } @@ -503,7 +538,7 @@ private fun getStatusBarContentBounds( @Rotation targetRotation: Int, @Rotation currentRotation: Int, bottomAlignedMargin: Int, - statusBarContentHeight: Int + statusBarContentHeight: Int, ): Rect { val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight) @@ -597,7 +632,7 @@ private val DisplayCutout.boundingRectsLeftRightTop private fun getInsetTop( bottomAlignedMargin: Int, statusBarContentHeight: Int, - statusBarHeight: Int + statusBarHeight: Int, ): Int { val bottomAlignmentEnabled = bottomAlignedMargin >= 0 if (!bottomAlignmentEnabled) { @@ -610,7 +645,7 @@ private fun getInsetTop( private fun sbRect( @Rotation relativeRotation: Int, sbHeight: Int, - displaySize: Pair<Int, Int> + displaySize: Pair<Int, Int>, ): Rect { val w = displaySize.first val h = displaySize.second @@ -626,7 +661,7 @@ private fun shareShortEdge( sbRect: Rect, cutoutRect: Rect, currentWidth: Int, - currentHeight: Int + currentHeight: Int, ): Boolean { if (currentWidth < currentHeight) { // Check top/bottom edges by extending the width of the display cutout rect and checking diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt index 7d0dadcf8c6e..7a88dcd92b88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt @@ -17,78 +17,40 @@ package com.android.systemui.statusbar.window import android.content.Context -import android.view.Display import android.view.WindowManager import com.android.app.viewcapture.ViewCaptureAwareWindowManager -import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore import com.android.systemui.statusbar.core.StatusBarConnectedDisplays -import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** Store that allows to retrieve per display instances of [StatusBarWindowController]. */ -interface StatusBarWindowControllerStore { - /** - * The instance for the default/main display of the device. For example, on a phone or a tablet, - * the default display is the internal/built-in display of the device. - * - * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. - */ - val defaultDisplay: StatusBarWindowController - - /** - * Returns an instance for a specific display id. - * - * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing - * displays. - */ - fun forDisplay(displayId: Int): StatusBarWindowController -} +interface StatusBarWindowControllerStore : PerDisplayStore<StatusBarWindowController> @SysUISingleton class MultiDisplayStatusBarWindowControllerStore @Inject constructor( - @Background private val backgroundApplicationScope: CoroutineScope, + @Background backgroundApplicationScope: CoroutineScope, private val controllerFactory: StatusBarWindowController.Factory, private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory, - private val displayRepository: DisplayRepository, -) : StatusBarWindowControllerStore, CoreStartable { + displayRepository: DisplayRepository, +) : + StatusBarWindowControllerStore, + PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) { init { StatusBarConnectedDisplays.assertInNewMode() } - private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>() - - override fun start() { - backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) { - displayRepository.displayRemovalEvent.collect { displayId -> - perDisplayControllers.remove(displayId) - } - } - } - - override val defaultDisplay: StatusBarWindowController - get() = forDisplay(Display.DEFAULT_DISPLAY) - - override fun forDisplay(displayId: Int): StatusBarWindowController { - if (displayRepository.getDisplay(displayId) == null) { - throw IllegalArgumentException("Display with id $displayId doesn't exist.") - } - return perDisplayControllers.computeIfAbsent(displayId) { - createControllerForDisplay(displayId) - } - } - - private fun createControllerForDisplay(displayId: Int): StatusBarWindowController { + override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController { val statusBarDisplayContext = displayWindowPropertiesRepository.get( displayId = displayId, @@ -101,6 +63,8 @@ constructor( viewCaptureAwareWindowManager, ) } + + override val instanceClass = StatusBarWindowController::class.java } @SysUISingleton @@ -110,16 +74,13 @@ constructor( context: Context, viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, factory: StatusBarWindowControllerImpl.Factory, -) : StatusBarWindowControllerStore { +) : + StatusBarWindowControllerStore, + PerDisplayStore<StatusBarWindowController> by SingleDisplayStore( + factory.create(context, viewCaptureAwareWindowManager) + ) { init { StatusBarConnectedDisplays.assertInLegacyMode() } - - private val controller: StatusBarWindowController = - factory.create(context, viewCaptureAwareWindowManager) - - override val defaultDisplay = controller - - override fun forDisplay(displayId: Int) = controller } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt deleted file mode 100644 index 0d1d37af7e5b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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.statusbar.core - -import android.platform.test.annotations.EnableFlags -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.display.data.repository.displayRepository -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope -import com.android.systemui.kosmos.unconfinedTestDispatcher -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -@SmallTest -@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) -class MultiDisplayStatusBarInitializerStoreTest : SysuiTestCase() { - - private val kosmos = - testKosmos().also { - // Using unconfinedTestDispatcher to avoid having to call `runCurrent` in the tests. - it.testDispatcher = it.unconfinedTestDispatcher - } - private val testScope = kosmos.testScope - private val fakeDisplayRepository = kosmos.displayRepository - private val store = kosmos.multiDisplayStatusBarInitializerStore - - @Before - fun start() { - store.start() - } - - @Before - fun addDisplays() = runBlocking { - fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID) - fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) - } - - @Test - fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = - testScope.runTest { - val controller = store.defaultDisplay - - assertThat(store.defaultDisplay).isSameInstanceAs(controller) - } - - @Test - fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = - testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) - } - - @Test - fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = - testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - - fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) - fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) - - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) - } - - @Test(expected = IllegalArgumentException::class) - fun forDisplay_nonExistingDisplayId_throws() = - testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } - - companion object { - private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY - private const val NON_DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1 - private const val NON_EXISTING_DISPLAY_ID = Display.DEFAULT_DISPLAY + 2 - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt new file mode 100644 index 000000000000..e3797260ed6d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -0,0 +1,49 @@ +/* + * 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.display.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import kotlinx.coroutines.CoroutineScope + +class FakePerDisplayStore( + backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, +) : PerDisplayStoreImpl<TestPerDisplayInstance>(backgroundApplicationScope, displayRepository) { + + val removalActions = mutableListOf<TestPerDisplayInstance>() + + override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance { + return TestPerDisplayInstance(displayId) + } + + override val instanceClass = TestPerDisplayInstance::class.java + + override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) { + removalActions += instance + } +} + +data class TestPerDisplayInstance(val displayId: Int) + +val Kosmos.fakePerDisplayStore by + Kosmos.Fixture { + FakePerDisplayStore( + backgroundApplicationScope = applicationCoroutineScope, + displayRepository = displayRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt index 8066b9138c99..303529b7f7b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt @@ -34,8 +34,8 @@ val Kosmos.multiDisplayStatusBarInitializerStore by Kosmos.Fixture { MultiDisplayStatusBarInitializerStore( applicationCoroutineScope, - fakeStatusBarInitializerFactory, displayRepository, + fakeStatusBarInitializerFactory, fakeStatusBarWindowControllerStore, ) } |