From adc0250c93aa508fc6f41b75e767e764169ef39d Mon Sep 17 00:00:00 2001 From: Nicolo' Mazzucato Date: Thu, 30 Nov 2023 16:09:19 +0000 Subject: Update mirroring dialog to show concurrent displays warning This introduces config_concurrentDisplayDeviceStates array, that is device specific and is supposed to contain all device states that represent "concurrent displays". This also creates a DeviceStateRepository that allows interactors to use the state provided by DeviceStateManager easily. Several other places in sysui are doing something similar (e.g. DevicePostureController and DisplayStateRepository), but with a slighly different logic that doesn't suit this use case (DPC is using an androidx related res that doesn't contain the concurrent state and having some logic to use the base state in certain cases, and DSR is only listening for specific states). Eventually, those other classes should be refactored to use DeviceStateRepository under the wood. This is only enabled for devices overriding the config_concurrentDisplayDeviceStates array in an overlay. Flag: ACONFIG enable_dual_display_blocking DISABLED Test: ConnectedDisplayInteractorTest, DeviceStateRepositoryTest, MirroringConfirmationDialogScerenshotTest Bug: 296211844 Change-Id: I68c0b1489019471aec0a72fda70f57a7bc1ed29d --- core/res/res/values/config.xml | 10 ++ core/res/res/values/symbols.xml | 1 + packages/SystemUI/Android.bp | 2 + .../res/layout/connected_display_dialog.xml | 9 ++ packages/SystemUI/res/values/strings.xml | 2 + .../data/repository/DisplayStateRepository.kt | 2 + .../com/android/systemui/display/DisplayModule.kt | 7 + .../data/repository/DeviceStateRepository.kt | 88 +++++++++++ .../interactor/ConnectedDisplayInteractor.kt | 12 ++ .../display/ui/view/MirroringConfirmationDialog.kt | 7 + .../ui/viewmodel/ConnectingDisplayViewModel.kt | 23 ++- .../data/repository/DeviceStateRepositoryTest.kt | 164 +++++++++++++++++++++ .../interactor/ConnectedDisplayInteractorTest.kt | 43 ++++++ .../statusbar/events/SystemEventCoordinatorTest.kt | 3 +- .../statusbar/phone/PhoneStatusBarPolicyTest.kt | 2 + .../data/repository/FakeDeviceStateRepository.kt | 31 ++++ 16 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ba1f3924bff3..1229453e5736 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -693,6 +693,16 @@ --> + + + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7787c5d394e4..93aacdff57df 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4142,6 +4142,7 @@ + diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7cf562f48ff3..c2c5e001a5df 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -190,6 +190,7 @@ android_library { "androidx.room_room-runtime", "androidx.room_room-ktx", "com.google.android.material_material", + "device_state_flags_lib", "kotlinx_coroutines_android", "kotlinx_coroutines", "iconloader_base", @@ -302,6 +303,7 @@ android_library { "androidx.exifinterface_exifinterface", "androidx.room_room-runtime", "androidx.room_room-ktx", + "device_state_flags_lib", "kotlinx-coroutines-android", "kotlinx-coroutines-core", "kotlinx_coroutines_test", diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index 3f65aa7984b5..8d7f7ebd82dc 100644 --- a/packages/SystemUI/res/layout/connected_display_dialog.xml +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -45,6 +45,15 @@ android:text="@string/connected_display_dialog_start_mirroring" android:textAppearance="@style/TextAppearance.Dialog.Title" /> + + Mirror to external display? + + Any dual screen activity currently running will be stopped Mirror display diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index ff23837703b5..b0143f5cdc4a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -60,6 +60,8 @@ interface DisplayStateRepository { val currentRotation: StateFlow } +// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository +// instead. @SysUISingleton class DisplayStateRepositoryImpl @Inject diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt index 65cd84bc4da1..373279cec5d1 100644 --- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt +++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.display +import com.android.systemui.display.data.repository.DeviceStateRepository +import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayRepositoryImpl import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor @@ -32,4 +34,9 @@ interface DisplayModule { ): ConnectedDisplayInteractor @Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository + + @Binds + fun bindsDeviceStateRepository( + deviceStateRepository: DeviceStateRepositoryImpl + ): DeviceStateRepository } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt new file mode 100644 index 000000000000..83337f760c33 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 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.content.Context +import android.hardware.devicestate.DeviceStateManager +import com.android.internal.R +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState +import java.util.concurrent.Executor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +interface DeviceStateRepository { + val state: StateFlow + + enum class DeviceState { + /** Device state in [R.array.config_foldedDeviceStates] */ + FOLDED, + /** Device state in [R.array.config_halfFoldedDeviceStates] */ + HALF_FOLDED, + /** Device state in [R.array.config_openDeviceStates] */ + UNFOLDED, + /** Device state in [R.array.config_rearDisplayDeviceStates] */ + REAR_DISPLAY, + /** Device state in [R.array.config_concurrentDisplayDeviceStates] */ + CONCURRENT_DISPLAY, + /** Device state in none of the other arrays. */ + UNKNOWN, + } +} + +class DeviceStateRepositoryImpl +@Inject +constructor( + context: Context, + deviceStateManager: DeviceStateManager, + @Background bgScope: CoroutineScope, + @Background executor: Executor +) : DeviceStateRepository { + + override val state: StateFlow = + conflatedCallbackFlow { + val callback = + DeviceStateManager.DeviceStateCallback { state -> + trySend(deviceStateToPosture(state)) + } + deviceStateManager.registerCallback(executor, callback) + awaitClose { deviceStateManager.unregisterCallback(callback) } + } + .stateIn(bgScope, started = SharingStarted.WhileSubscribed(), DeviceState.UNKNOWN) + + private fun deviceStateToPosture(deviceStateId: Int): DeviceState { + return deviceStateMap.firstOrNull { (ids, _) -> deviceStateId in ids }?.deviceState + ?: DeviceState.UNKNOWN + } + + private val deviceStateMap = + listOf( + R.array.config_foldedDeviceStates to DeviceState.FOLDED, + R.array.config_halfFoldedDeviceStates to DeviceState.HALF_FOLDED, + R.array.config_openDeviceStates to DeviceState.UNFOLDED, + R.array.config_rearDisplayDeviceStates to DeviceState.REAR_DISPLAY, + R.array.config_concurrentDisplayDeviceStates to DeviceState.CONCURRENT_DISPLAY, + ) + .map { IdsPerDeviceState(context.resources.getIntArray(it.first).toSet(), it.second) } + + private data class IdsPerDeviceState(val ids: Set, val deviceState: DeviceState) +} diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt index 20a9e5d572c9..73b7a8ac7bd3 100644 --- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt @@ -21,6 +21,7 @@ import android.companion.virtual.flags.Flags import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State @@ -55,6 +56,9 @@ interface ConnectedDisplayInteractor { /** Pending display that can be enabled to be used by the system. */ val pendingDisplay: Flow + /** Pending display that can be enabled to be used by the system. */ + val concurrentDisplaysInProgress: Flow + /** Possible connected display state. */ enum class State { DISCONNECTED, @@ -84,6 +88,7 @@ constructor( private val virtualDeviceManager: VirtualDeviceManager, keyguardRepository: KeyguardRepository, displayRepository: DisplayRepository, + deviceStateRepository: DeviceStateRepository, @Background backgroundCoroutineDispatcher: CoroutineDispatcher, ) : ConnectedDisplayInteractor { @@ -128,9 +133,16 @@ constructor( } } + override val concurrentDisplaysInProgress: Flow = + deviceStateRepository.state + .map { it == DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY } + .distinctUntilChanged() + .flowOn(backgroundCoroutineDispatcher) + private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay = object : PendingDisplay { override suspend fun enable() = this@toInteractorPendingDisplay.enable() + override suspend fun ignore() = this@toInteractorPendingDisplay.ignore() } diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index d500d1c2d238..c0a873ac9a65 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -37,11 +37,13 @@ class MirroringConfirmationDialog( private val onCancelMirroring: View.OnClickListener, private val navbarBottomInsetsProvider: () -> Int, configurationController: ConfigurationController? = null, + private val showConcurrentDisplayInfo: Boolean = false, theme: Int = R.style.Theme_SystemUI_Dialog, ) : SystemUIBottomSheetDialog(context, configurationController, theme) { private lateinit var mirrorButton: TextView private lateinit var dismissButton: TextView + private lateinit var dualDisplayWarning: TextView private var enabledPressed = false override fun onCreate(savedInstanceState: Bundle?) { @@ -56,6 +58,11 @@ class MirroringConfirmationDialog( dismissButton = requireViewById(R.id.cancel).apply { setOnClickListener(onCancelMirroring) } + dualDisplayWarning = + requireViewById(R.id.dual_display_warning).apply { + visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE + } + setOnDismissListener { if (!enabledPressed) { onCancelMirroring.onClick(null) diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt index 19b4d2220558..10aa70391f01 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.display.ui.viewmodel import android.app.Dialog import android.content.Context +import com.android.server.policy.feature.flags.Flags import com.android.systemui.biometrics.Utils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -28,8 +29,9 @@ import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** @@ -44,25 +46,33 @@ constructor( private val connectedDisplayInteractor: ConnectedDisplayInteractor, @Application private val scope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, - private val configurationController: ConfigurationController + private val configurationController: ConfigurationController, ) { private var dialog: Dialog? = null /** Starts listening for pending displays. */ fun init() { - connectedDisplayInteractor.pendingDisplay - .onEach { pendingDisplay -> + val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay + val concurrentDisplaysInProgessFlow = + if (Flags.enableDualDisplayBlocking()) { + connectedDisplayInteractor.concurrentDisplaysInProgress + } else { + flow { emit(false) } + } + pendingDisplayFlow + .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress + -> if (pendingDisplay == null) { hideDialog() } else { - showDialog(pendingDisplay) + showDialog(pendingDisplay, concurrentDisplaysInProgress) } } .launchIn(scope) } - private fun showDialog(pendingDisplay: PendingDisplay) { + private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) { hideDialog() dialog = MirroringConfirmationDialog( @@ -77,6 +87,7 @@ constructor( }, navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom }, configurationController, + showConcurrentDisplayInfo = concurrentDisplaysInProgess ) .apply { show() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt new file mode 100644 index 000000000000..21b8aca363ca --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 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.hardware.devicestate.DeviceStateManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.FlowValue +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +class DeviceStateRepositoryTest : SysuiTestCase() { + + private val deviceStateManager = mock() + private val deviceStateManagerListener = + kotlinArgumentCaptor() + + private val testScope = TestScope(UnconfinedTestDispatcher()) + private val fakeExecutor = FakeExecutor(FakeSystemClock()) + + private lateinit var deviceStateRepository: DeviceStateRepositoryImpl + + @Before + fun setup() { + mContext.orCreateTestableResources.apply { + addOverride(R.array.config_foldedDeviceStates, listOf(TEST_FOLDED).toIntArray()) + addOverride(R.array.config_halfFoldedDeviceStates, TEST_HALF_FOLDED.toIntArray()) + addOverride(R.array.config_openDeviceStates, TEST_UNFOLDED.toIntArray()) + addOverride(R.array.config_rearDisplayDeviceStates, TEST_REAR_DISPLAY.toIntArray()) + addOverride( + R.array.config_concurrentDisplayDeviceStates, + TEST_CONCURRENT_DISPLAY.toIntArray() + ) + } + deviceStateRepository = + DeviceStateRepositoryImpl( + mContext, + deviceStateManager, + TestScope(UnconfinedTestDispatcher()), + fakeExecutor + ) + + // It should register only after there are clients collecting the flow + verify(deviceStateManager, never()).registerCallback(any(), any()) + } + + @Test + fun folded_receivesFoldedState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(TEST_FOLDED) + + assertThat(state()).isEqualTo(DeviceState.FOLDED) + } + + @Test + fun halfFolded_receivesHalfFoldedState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED) + + assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED) + } + + @Test + fun unfolded_receivesUnfoldedState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED) + + assertThat(state()).isEqualTo(DeviceState.UNFOLDED) + } + + @Test + fun rearDisplay_receivesRearDisplayState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY) + + assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY) + } + + @Test + fun concurrentDisplay_receivesConcurrentDisplayState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY) + + assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY) + } + + @Test + fun unknownState_receivesUnknownState() = + testScope.runTest { + val state = displayState() + + deviceStateManagerListener.value.onStateChanged(123456) + + assertThat(state()).isEqualTo(DeviceState.UNKNOWN) + } + + private fun TestScope.displayState(): FlowValue { + val flowValue = collectLastValue(deviceStateRepository.state) + verify(deviceStateManager) + .registerCallback( + any(), + deviceStateManagerListener.capture(), + ) + return flowValue + } + + private fun Int.toIntArray() = listOf(this).toIntArray() + + private companion object { + // Used to fake the ids in the test. Note that there is no guarantees different devices will + // have the same ids (that's why the ones in this test start from 41) + const val TEST_FOLDED = 41 + const val TEST_HALF_FOLDED = 42 + const val TEST_UNFOLDED = 43 + const val TEST_REAR_DISPLAY = 44 + const val TEST_CONCURRENT_DISPLAY = 45 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt index 1f18705edfdb..42b0f5097cad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt @@ -28,6 +28,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.DeviceStateRepository +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY +import com.android.systemui.display.data.repository.FakeDeviceStateRepository import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.display.data.repository.createPendingDisplay import com.android.systemui.display.data.repository.display @@ -59,11 +62,13 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { private val fakeDisplayRepository = FakeDisplayRepository() private val fakeKeyguardRepository = FakeKeyguardRepository() + private val fakeDeviceStateRepository = FakeDeviceStateRepository() private val connectedDisplayStateProvider: ConnectedDisplayInteractor = ConnectedDisplayInteractorImpl( virtualDeviceManager, fakeKeyguardRepository, fakeDisplayRepository, + fakeDeviceStateRepository, UnconfinedTestDispatcher(), ) private val testScope = TestScope(UnconfinedTestDispatcher()) @@ -283,6 +288,44 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { assertThat(pendingDisplay).isNull() } + @Test + fun concurrentDisplaysInProgress_started_returnsTrue() = + testScope.runTest { + val concurrentDisplaysInProgress = + collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress) + + fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY) + + assertThat(concurrentDisplaysInProgress()).isTrue() + } + + @Test + fun concurrentDisplaysInProgress_stopped_returnsFalse() = + testScope.runTest { + val concurrentDisplaysInProgress = + collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress) + + fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY) + fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN) + + assertThat(concurrentDisplaysInProgress()).isFalse() + } + + @Test + fun concurrentDisplaysInProgress_otherStates_returnsFalse() = + testScope.runTest { + val concurrentDisplaysInProgress = + collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress) + + DeviceStateRepository.DeviceState.entries + .filter { it != CONCURRENT_DISPLAY } + .forEach { deviceState -> + fakeDeviceStateRepository.emit(deviceState) + + assertThat(concurrentDisplaysInProgress()).isFalse() + } + } + private fun TestScope.lastValue(): FlowValue = collectLastValue(connectedDisplayStateProvider.connectedDisplayState) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt index bbc63f2009b9..ae84df55e113 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt @@ -21,7 +21,6 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay -import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.mockito.any @@ -107,5 +106,7 @@ class SystemEventCoordinatorTest : SysuiTestCase() { get() = flow override val pendingDisplay: Flow get() = MutableSharedFlow() + override val concurrentDisplaysInProgress: Flow + get() = TODO("Not yet implemented") } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index da6c28ad9af4..7deee5a70809 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -321,5 +321,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { get() = TODO("Not yet implemented") override val pendingDisplay: Flow get() = TODO("Not yet implemented") + override val concurrentDisplaysInProgress: Flow + get() = TODO("Not yet implemented") } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt new file mode 100644 index 000000000000..5f6dc6e7d429 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Fake [DeviceStateRepository] implementation for testing. */ +class FakeDeviceStateRepository : DeviceStateRepository { + private val flow = MutableStateFlow(DeviceStateRepository.DeviceState.UNKNOWN) + + /** Emits [value] as [displays] flow value. */ + suspend fun emit(value: DeviceStateRepository.DeviceState) = flow.emit(value) + + override val state: StateFlow + get() = flow +} -- cgit v1.2.3-59-g8ed1b