summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt133
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt228
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt12
8 files changed, 394 insertions, 127 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index e7bbf97e3b51..f68078a8a340 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@ import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
import android.os.Handler
+import android.os.Trace
import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -34,9 +35,14 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -49,7 +55,25 @@ interface DisplayRepository {
*
* When `null`, it means there is no pending display waiting to be enabled.
*/
- val pendingDisplayId: Flow<Int?>
+ val pendingDisplay: Flow<PendingDisplay?>
+
+ /** Represents a connected display that has not been enabled yet. */
+ interface PendingDisplay {
+ /** Id of the pending display. */
+ val id: Int
+
+ /** Enables the display, making it available to the system. */
+ suspend fun enable()
+
+ /**
+ * Ignores the pending display. When called, this specific display id doesn't appear as
+ * pending anymore until the display is disconnected and reconnected again.
+ */
+ suspend fun ignore()
+
+ /** Disables the display, making it unavailable to the system. */
+ suspend fun disable()
+ }
}
@SysUISingleton
@@ -62,7 +86,8 @@ constructor(
@Background backgroundCoroutineDispatcher: CoroutineDispatcher
) : DisplayRepository {
- override val displays: Flow<Set<Display>> =
+ // Displays are enabled only after receiving them in [onDisplayAdded]
+ private val enabledDisplays: StateFlow<Set<Display>> =
conflatedCallbackFlow {
val callback =
object : DisplayListener {
@@ -99,27 +124,38 @@ constructor(
displayManager.displays?.toSet() ?: emptySet()
}
- override val pendingDisplayId: Flow<Int?> =
+ /** Propagate to the listeners only enabled displays */
+ override val displays: Flow<Set<Display>> = enabledDisplays
+
+ private val enabledDisplayIds: Flow<Set<Int>> =
+ enabledDisplays
+ .map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
+ .debugLog("enabledDisplayIds")
+
+ private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
+
+ /* keeps connected displays until they are disconnected. */
+ private val connectedDisplayIds: StateFlow<Set<Int>> =
conflatedCallbackFlow {
val callback =
object : DisplayConnectionListener {
- private val pendingIds = mutableSetOf<Int>()
+ private val connectedIds = mutableSetOf<Int>()
override fun onDisplayConnected(id: Int) {
- pendingIds += id
- trySend(id)
+ if (DEBUG) {
+ Log.d(TAG, "$id connected")
+ }
+ connectedIds += id
+ ignoredDisplayIds.value -= id
+ trySend(connectedIds.toSet())
}
override fun onDisplayDisconnected(id: Int) {
- if (id in pendingIds) {
- pendingIds -= id
- trySend(null)
- } else {
- Log.e(
- TAG,
- "onDisplayDisconnected received for unknown display. " +
- "id=$id, knownIds=$pendingIds"
- )
+ connectedIds -= id
+ if (DEBUG) {
+ Log.d(TAG, "$id disconnected. Connected ids: $connectedIds")
}
+ ignoredDisplayIds.value -= id
+ trySend(connectedIds.toSet())
}
}
displayManager.registerDisplayListener(
@@ -130,15 +166,80 @@ constructor(
awaitClose { displayManager.unregisterDisplayListener(callback) }
}
.distinctUntilChanged()
+ .debugLog("connectedDisplayIds")
.flowOn(backgroundCoroutineDispatcher)
.stateIn(
applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = null
+ initialValue = emptySet()
)
+ /**
+ * Pending displays are the ones connected, but not enabled and not ignored. A connected display
+ * is ignored after the user makes the decision to use it or not. For now, the initial decision
+ * from the user is final and not reversible.
+ */
+ private val pendingDisplayIds: Flow<Set<Int>> =
+ combine(enabledDisplayIds, connectedDisplayIds, ignoredDisplayIds) {
+ enabledDisplaysIds,
+ connectedDisplayIds,
+ ignoredDisplayIds ->
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "combining enabled: $enabledDisplaysIds, " +
+ "connected: $connectedDisplayIds, ignored: $ignoredDisplayIds"
+ )
+ }
+ connectedDisplayIds - enabledDisplaysIds - ignoredDisplayIds
+ }
+ .debugLog("pendingDisplayIds")
+
+ override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?> =
+ pendingDisplayIds
+ .map { pendingDisplayIds ->
+ val id = pendingDisplayIds.maxOrNull() ?: return@map null
+ object : DisplayRepository.PendingDisplay {
+ override val id = id
+ override suspend fun enable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.enableConnectedDisplay(id)
+ }
+ // After the display has been enabled, it is automatically ignored.
+ ignore()
+ }
+
+ override suspend fun ignore() {
+ traceSection("DisplayRepository#ignore($id)") {
+ ignoredDisplayIds.value += id
+ }
+ }
+
+ override suspend fun disable() {
+ ignore()
+ traceSection("DisplayRepository#disable($id)") {
+ displayManager.disableConnectedDisplay(id)
+ }
+ }
+ }
+ }
+ .debugLog("pendingDisplay")
+
+ private fun <T> Flow<T>.debugLog(flowName: String): Flow<T> {
+ return if (DEBUG) {
+ this.onEach {
+ Log.d(TAG, "$flowName: $it")
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "$TAG#$flowName", 0)
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "$TAG#$flowName", "$it", 0)
+ }
+ } else {
+ this
+ }
+ }
+
private companion object {
const val TAG = "DisplayRepository"
+ val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
}
}
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 ef6fa26420a3..11ed96d5c7eb 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
@@ -16,14 +16,12 @@
package com.android.systemui.display.domain.interactor
-import android.hardware.display.DisplayManager
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
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
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.util.traceSection
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -52,13 +50,18 @@ interface ConnectedDisplayInteractor {
CONNECTED_SECURE,
}
- /** Represents a connected display that has not been enabled yet. */
+ /** Represents a connected display that has not been enabled yet for the UI layer. */
interface PendingDisplay {
/** Enables the display, making it available to the system. */
- fun enable()
+ suspend fun enable()
- /** Disables the display, making it unavailable to the system. */
- fun disable()
+ /**
+ * Ignores the pending display.
+ *
+ * When called, this specific display id doesn't appear as pending anymore until the display
+ * is disconnected and reconnected again.
+ */
+ suspend fun ignore()
}
}
@@ -66,7 +69,6 @@ interface ConnectedDisplayInteractor {
class ConnectedDisplayInteractorImpl
@Inject
constructor(
- private val displayManager: DisplayManager,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -92,28 +94,19 @@ constructor(
// Provides the pending display only if the lockscreen is unlocked
override val pendingDisplay: Flow<PendingDisplay?> =
- displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
- pendingDisplayId,
- keyguardUnlocked ->
- if (pendingDisplayId != null && keyguardUnlocked) {
- pendingDisplayId.toPendingDisplay()
+ displayRepository.pendingDisplay.combine(keyguardRepository.isKeyguardShowing) {
+ repositoryPendingDisplay,
+ keyguardShowing ->
+ if (repositoryPendingDisplay != null && !keyguardShowing) {
+ repositoryPendingDisplay.toInteractorPendingDisplay()
} else {
null
}
}
- private fun Int.toPendingDisplay() =
+ private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
object : PendingDisplay {
- val id = this@toPendingDisplay
- override fun enable() {
- traceSection("DisplayRepository#enable($id)") {
- displayManager.enableConnectedDisplay(id)
- }
- }
- override fun disable() {
- traceSection("DisplayRepository#enable($id)") {
- displayManager.disableConnectedDisplay(id)
- }
- }
+ 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 174c6ff04a7d..ecc9d0ef7810 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
@@ -24,15 +24,22 @@ import android.view.WindowManager
import android.widget.TextView
import com.android.systemui.R
-/** Dialog used to decide what to do with a connected display. */
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
class MirroringConfirmationDialog(
context: Context,
private val onStartMirroringClickListener: View.OnClickListener,
- private val onDismissClickListener: View.OnClickListener,
+ private val onCancelMirroring: View.OnClickListener,
) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
+ private var enabledPressed = false
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.apply {
@@ -45,10 +52,15 @@ class MirroringConfirmationDialog(
mirrorButton =
requireViewById<TextView>(R.id.enable_display).apply {
setOnClickListener(onStartMirroringClickListener)
+ enabledPressed = true
}
dismissButton =
- requireViewById<TextView>(R.id.cancel).apply {
- setOnClickListener(onDismissClickListener)
+ requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
+
+ 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 ece33b7f6032..86ef439361b0 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
@@ -19,13 +19,16 @@ import android.app.Dialog
import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.MirroringConfirmationDialog
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
/**
* Shows/hides a dialog to allow the user to decide whether to use the external display for
@@ -38,6 +41,7 @@ constructor(
private val context: Context,
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher
) {
private var dialog: Dialog? = null
@@ -61,10 +65,13 @@ constructor(
MirroringConfirmationDialog(
context,
onStartMirroringClickListener = {
- pendingDisplay.enable()
+ scope.launch(bgDispatcher) { pendingDisplay.enable() }
hideDialog()
},
- onDismissClickListener = { hideDialog() }
+ onCancelMirroring = {
+ scope.launch(bgDispatcher) { pendingDisplay.ignore() }
+ hideDialog()
+ }
)
.apply { show() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index db7c003ee545..4bd380e927c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -52,6 +52,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+ private val connectedDisplayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
private val testHandler = FakeHandler(Looper.getMainLooper())
private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -114,7 +115,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
// Let's make sure it has *NOT* been unregistered, as there is still a subscriber.
setDisplays(1)
- displayListener.value.onDisplayAdded(1)
+ sendOnDisplayAdded(1)
assertThat(firstSubscriber?.ids()).containsExactly(1)
}
@@ -127,7 +128,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
val value by latestDisplayFlowValue()
setDisplays(1)
- displayListener.value.onDisplayAdded(1)
+ sendOnDisplayAdded(1)
assertThat(value?.ids()).containsExactly(1)
}
@@ -138,13 +139,13 @@ class DisplayRepositoryTest : SysuiTestCase() {
val value by latestDisplayFlowValue()
setDisplays(1, 2, 3, 4)
- displayListener.value.onDisplayAdded(1)
- displayListener.value.onDisplayAdded(2)
- displayListener.value.onDisplayAdded(3)
- displayListener.value.onDisplayAdded(4)
+ sendOnDisplayAdded(1)
+ sendOnDisplayAdded(2)
+ sendOnDisplayAdded(3)
+ sendOnDisplayAdded(4)
setDisplays(1, 2, 3)
- displayListener.value.onDisplayRemoved(4)
+ sendOnDisplayRemoved(4)
assertThat(value?.ids()).containsExactly(1, 2, 3)
}
@@ -155,10 +156,10 @@ class DisplayRepositoryTest : SysuiTestCase() {
val value by latestDisplayFlowValue()
setDisplays(1, 2, 3, 4)
- displayListener.value.onDisplayAdded(1)
- displayListener.value.onDisplayAdded(2)
- displayListener.value.onDisplayAdded(3)
- displayListener.value.onDisplayAdded(4)
+ sendOnDisplayAdded(1)
+ sendOnDisplayAdded(2)
+ sendOnDisplayAdded(3)
+ sendOnDisplayAdded(4)
displayListener.value.onDisplayChanged(4)
@@ -168,22 +169,22 @@ class DisplayRepositoryTest : SysuiTestCase() {
@Test
fun onDisplayConnected_pendingDisplayReceived() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
+ val pendingDisplay by lastPendingDisplay()
- displayListener.value.onDisplayConnected(1)
+ sendOnDisplayConnected(1)
- assertThat(pendingDisplay).isEqualTo(1)
+ assertThat(pendingDisplay!!.id).isEqualTo(1)
}
@Test
fun onDisplayDisconnected_pendingDisplayNull() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
assertThat(pendingDisplay).isNotNull()
- displayListener.value.onDisplayDisconnected(1)
+ sendOnDisplayDisconnected(1)
assertThat(pendingDisplay).isNull()
}
@@ -191,24 +192,162 @@ class DisplayRepositoryTest : SysuiTestCase() {
@Test
fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
assertThat(pendingDisplay).isNotNull()
- displayListener.value.onDisplayDisconnected(2)
+ sendOnDisplayDisconnected(2)
assertThat(pendingDisplay).isNotNull()
}
@Test
- fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+ fun onDisplayConnected_multipleTimes_sendsOnlyTheMaximum() =
testScope.runTest {
- val pendingDisplay by latestPendingDisplayFlowValue()
- displayListener.value.onDisplayConnected(1)
- displayListener.value.onDisplayConnected(2)
+ val pendingDisplay by lastPendingDisplay()
- assertThat(pendingDisplay).isEqualTo(2)
+ sendOnDisplayConnected(1)
+ sendOnDisplayConnected(2)
+
+ assertThat(pendingDisplay!!.id).isEqualTo(2)
+ }
+
+ @Test
+ fun onPendingDisplay_enable_displayEnabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.enable()
+
+ verify(displayManager).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_enableBySysui_disabledBySomeoneElse_pendingDisplayStillIgnored() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.enable()
+ // to mock the display being really enabled:
+ sendOnDisplayAdded(1)
+
+ // Simulate the display being disabled by someone else. Now, sysui will have it in the
+ // "pending displays" list again, but it should be ignored.
+ sendOnDisplayRemoved(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_ignoredBySysui_enabledDisabledBySomeoneElse_pendingDisplayStillIgnored() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.ignore()
+
+ // to mock the display being enabled and disabled by someone else:
+ sendOnDisplayAdded(1)
+ sendOnDisplayRemoved(1)
+
+ // Sysui already decided to ignore it, so the pending display should be null.
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_disable_displayDisabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ pendingDisplay!!.disable()
+
+ verify(displayManager).disableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_ignore_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+ sendOnDisplayConnected(1)
+
+ pendingDisplay!!.ignore()
+
+ assertThat(pendingDisplay).isNull()
+ verify(displayManager, never()).disableConnectedDisplay(eq(1))
+ verify(displayManager, never()).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_enabled_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ assertThat(pendingDisplay).isNotNull()
+
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onPendingDisplay_multipleConnected_oneEnabled_pendingDisplayNotNull() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ sendOnDisplayConnected(1)
+ sendOnDisplayConnected(2)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ assertThat(pendingDisplay).isNotNull()
+ assertThat(pendingDisplay!!.id).isEqualTo(2)
+
+ setDisplays(1, 2)
+ sendOnDisplayAdded(2)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun pendingDisplay_connectedDisconnectedAndReconnected_expectedPendingDisplayState() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ // Plug the cable
+ sendOnDisplayConnected(1)
+
+ // Enable it
+ assertThat(pendingDisplay).isNotNull()
+ pendingDisplay!!.enable()
+
+ // Enabled
+ verify(displayManager).enableConnectedDisplay(1)
+ setDisplays(1)
+ sendOnDisplayAdded(1)
+
+ // No more pending displays
+ assertThat(pendingDisplay).isNull()
+
+ // Let's disconnect the cable
+ setDisplays()
+ sendOnDisplayRemoved(1)
+ sendOnDisplayDisconnected(1)
+
+ assertThat(pendingDisplay).isNull()
+
+ // Let's reconnect it
+ sendOnDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
}
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
@@ -216,28 +355,47 @@ class DisplayRepositoryTest : SysuiTestCase() {
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
+ captureAddedRemovedListener()
+ return flowValue
+ }
+
+ private fun TestScope.lastPendingDisplay(): FlowValue<DisplayRepository.PendingDisplay?> {
+ val flowValue = collectLastValue(displayRepository.pendingDisplay)
+ captureAddedRemovedListener()
verify(displayManager)
.registerDisplayListener(
- displayListener.capture(),
+ connectedDisplayListener.capture(),
eq(testHandler),
- eq(
- DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
- DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
- DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
- )
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
)
return flowValue
}
- private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
- val flowValue = collectLastValue(displayRepository.pendingDisplayId)
+ private fun captureAddedRemovedListener() {
verify(displayManager)
.registerDisplayListener(
displayListener.capture(),
eq(testHandler),
- eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ eq(
+ DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
+ DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+ )
)
- return flowValue
+ }
+ private fun sendOnDisplayAdded(id: Int) {
+ displayListener.value.onDisplayAdded(id)
+ }
+ private fun sendOnDisplayRemoved(id: Int) {
+ displayListener.value.onDisplayRemoved(id)
+ }
+
+ private fun sendOnDisplayDisconnected(id: Int) {
+ connectedDisplayListener.value.onDisplayDisconnected(id)
+ }
+
+ private fun sendOnDisplayConnected(id: Int) {
+ connectedDisplayListener.value.onDisplayConnected(id)
}
private fun setDisplays(displays: List<Display>) {
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 50617a16ce0b..26ee09412687 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
@@ -16,7 +16,6 @@
package com.android.systemui.display.domain.interactor
-import android.hardware.display.DisplayManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -27,12 +26,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.createPendingDisplay
import com.android.systemui.display.data.repository.display
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -41,7 +39,6 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -49,20 +46,15 @@ import org.mockito.Mockito
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
- private val displayManager = mock<DisplayManager>()
private val fakeDisplayRepository = FakeDisplayRepository()
private val fakeKeyguardRepository = FakeKeyguardRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(
- displayManager,
- fakeKeyguardRepository,
- fakeDisplayRepository
- )
+ ConnectedDisplayInteractorImpl(fakeKeyguardRepository, fakeDisplayRepository)
private val testScope = TestScope(UnconfinedTestDispatcher())
@Before
fun setup() {
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
}
@Test
@@ -148,7 +140,7 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() {
fun pendingDisplay_propagated() =
testScope.runTest {
val value by lastPendingDisplay()
- val pendingDisplayId = 4
+ val pendingDisplayId = createPendingDisplay()
fakeDisplayRepository.emit(pendingDisplayId)
@@ -156,51 +148,29 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() {
}
@Test
- fun onPendingDisplay_enable_displayEnabled() =
+ fun onPendingDisplay_keyguardShowing_returnsPendingDisplay() =
testScope.runTest {
+ fakeKeyguardRepository.setKeyguardShowing(true)
val pendingDisplay by lastPendingDisplay()
- fakeDisplayRepository.emit(1)
- pendingDisplay!!.enable()
-
- Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
- }
-
- @Test
- fun onPendingDisplay_disable_displayDisabled() =
- testScope.runTest {
- val pendingDisplay by lastPendingDisplay()
-
- fakeDisplayRepository.emit(1)
- pendingDisplay!!.disable()
-
- Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
- }
-
- @Test
- fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
- testScope.runTest {
- fakeKeyguardRepository.setKeyguardUnlocked(false)
- val pendingDisplay by lastPendingDisplay()
-
- fakeDisplayRepository.emit(1)
+ fakeDisplayRepository.emit(createPendingDisplay())
assertThat(pendingDisplay).isNull()
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
assertThat(pendingDisplay).isNotNull()
}
@Test
- fun onPendingDisplay_keyguardLocked_returnsNull() =
+ fun onPendingDisplay_keyguardShowing_returnsNull() =
testScope.runTest {
- fakeKeyguardRepository.setKeyguardUnlocked(true)
+ fakeKeyguardRepository.setKeyguardShowing(false)
val pendingDisplay by lastPendingDisplay()
- fakeDisplayRepository.emit(1)
+ fakeDisplayRepository.emit(createPendingDisplay())
assertThat(pendingDisplay).isNotNull()
- fakeKeyguardRepository.setKeyguardUnlocked(false)
+ fakeKeyguardRepository.setKeyguardShowing(true)
assertThat(pendingDisplay).isNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index 705964736a49..46f758216aae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -68,6 +68,28 @@ class MirroringConfirmationDialogTest : SysuiTestCase() {
verify(onStartMirroringCallback, never()).onClick(any())
}
+ @Test
+ fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+ dialog.show()
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ dialog.cancel()
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+ dialog.show()
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ dialog.dismiss()
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 6f51d1b60822..2ac625d68bfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -30,20 +30,24 @@ fun display(type: Int, flags: Int = 0, id: Int = 0): Display {
}
}
+/** Creates a mock [DisplayRepository.PendingDisplay]. */
+fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
+ mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
+
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
- private val pendingDisplayFlow = MutableSharedFlow<Int?>()
+ private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
- /** Emits [value] as [pendingDisplayId] flow value. */
- suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+ /** Emits [value] as [pendingDisplay] flow value. */
+ suspend fun emit(value: DisplayRepository.PendingDisplay?) = pendingDisplayFlow.emit(value)
override val displays: Flow<Set<Display>>
get() = flow
- override val pendingDisplayId: Flow<Int?>
+ override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
}