Merge "Update mirroring dialog to show concurrent displays warning" into main
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ba1f392..1229453 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -693,6 +693,16 @@
-->
</integer-array>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as concurrent
+ display state. Default is empty. -->
+ <integer-array name="config_concurrentDisplayDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
+
<!-- Indicates whether the window manager reacts to half-fold device states by overriding
rotation. -->
<bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7787c5d..93aacdf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4142,6 +4142,7 @@
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="array" name="config_halfFoldedDeviceStates" />
<java-symbol type="array" name="config_rearDisplayDeviceStates" />
+ <java-symbol type="array" name="config_concurrentDisplayDeviceStates" />
<java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
<java-symbol type="bool" name="config_windowManagerPauseRotationWhenUnfolding" />
<java-symbol type="integer" name="config_pauseRotationWhenUnfolding_hingeEventTimeout" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7cf562f..c2c5e00 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -190,6 +190,7 @@
"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 @@
"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 3f65aa7..8d7f7eb 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" />
+ <TextView
+ android:id="@+id/dual_display_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:visibility="gone"
+ android:text="@string/connected_display_dialog_dual_display_stop_warning"
+ android:textAppearance="@style/TextAppearance.Dialog.Body" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f49d2a1..7ca0b6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3245,6 +3245,8 @@
<!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
<string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+ <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
+ <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="mirror_display">Mirror display</string>
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
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 ff23837..b0143f5 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 @@
val currentRotation: StateFlow<DisplayRotation>
}
+// 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 65cd84b..373279c 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 @@
): 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 0000000..83337f7
--- /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<DeviceState>
+
+ 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<DeviceState> =
+ 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<Int>, 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 20a9e5d..73b7a8a 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.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 @@
/** Pending display that can be enabled to be used by the system. */
val pendingDisplay: Flow<PendingDisplay?>
+ /** Pending display that can be enabled to be used by the system. */
+ val concurrentDisplaysInProgress: Flow<Boolean>
+
/** Possible connected display state. */
enum class State {
DISCONNECTED,
@@ -84,6 +88,7 @@
private val virtualDeviceManager: VirtualDeviceManager,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
+ deviceStateRepository: DeviceStateRepository,
@Background backgroundCoroutineDispatcher: CoroutineDispatcher,
) : ConnectedDisplayInteractor {
@@ -128,9 +133,16 @@
}
}
+ override val concurrentDisplaysInProgress: Flow<Boolean> =
+ 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 d500d1c2..c0a873a 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 @@
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 @@
dismissButton =
requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
+ dualDisplayWarning =
+ requireViewById<TextView>(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 19b4d22..10aa703 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 @@
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 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 @@
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 @@
},
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 0000000..21b8aca
--- /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<DeviceStateManager>()
+ private val deviceStateManagerListener =
+ kotlinArgumentCaptor<DeviceStateManager.DeviceStateCallback>()
+
+ 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<DeviceState?> {
+ 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 1f18705..42b0f50 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 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 @@
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 @@
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<State?> =
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 bbc63f2..ae84df5 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 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 @@
get() = flow
override val pendingDisplay: Flow<PendingDisplay?>
get() = MutableSharedFlow<PendingDisplay>()
+ override val concurrentDisplaysInProgress: Flow<Boolean>
+ 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 da6c28a..7deee5a 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 @@
get() = TODO("Not yet implemented")
override val pendingDisplay: Flow<PendingDisplay?>
get() = TODO("Not yet implemented")
+ override val concurrentDisplaysInProgress: Flow<Boolean>
+ 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 0000000..5f6dc6e
--- /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<DeviceStateRepository.DeviceState>
+ get() = flow
+}