summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt63
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt2
3 files changed, 201 insertions, 3 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index dfea78458b9d..197b0eea05cb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -23,15 +23,16 @@ import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.Display.TYPE_EXTERNAL
import android.view.Display.TYPE_INTERNAL
+import android.view.IWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
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.coroutines.collectValues
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -46,6 +47,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -53,7 +55,11 @@ import org.mockito.kotlin.eq
class DisplayRepositoryTest : SysuiTestCase() {
private val displayManager = mock<DisplayManager>()
+ private val commandQueue = mock<CommandQueue>()
+ private val windowManager = mock<IWindowManager>()
+
private val displayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
+ private val commandQueueCallbacks = kotlinArgumentCaptor<CommandQueue.Callbacks>()
private val connectedDisplayListener = kotlinArgumentCaptor<DisplayManager.DisplayListener>()
private val testHandler = FakeHandler(Looper.getMainLooper())
@@ -67,6 +73,8 @@ class DisplayRepositoryTest : SysuiTestCase() {
private val displayRepository: DisplayRepositoryImpl by lazy {
DisplayRepositoryImpl(
displayManager,
+ commandQueue,
+ windowManager,
testHandler,
TestScope(UnconfinedTestDispatcher()),
UnconfinedTestDispatcher(),
@@ -513,6 +521,115 @@ class DisplayRepositoryTest : SysuiTestCase() {
assertThat(displayRepository.getDisplay(2)).isNull()
}
+ @Test
+ fun displayIdsWithSystemDecorations_onStart_emitsDisplaysWithSystemDecorations() =
+ testScope.runTest {
+ setDisplays(0, 1, 2)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(1)).thenReturn(false)
+ whenever(windowManager.shouldShowSystemDecors(2)).thenReturn(true)
+
+ val displayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(displayIdsWithSystemDecorations).containsExactly(0, 2)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationAdded_emitsIncludingNewDisplayIds() =
+ testScope.runTest {
+ setDisplays(0)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayAddSystemDecorations(3)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(0, 2, 3)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationAdded_emitsToNewSubscribers() =
+ testScope.runTest {
+ setDisplays(0)
+ whenever(windowManager.shouldShowSystemDecors(0)).thenReturn(true)
+
+ val priorDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+ sendOnDisplayAddSystemDecorations(1)
+ assertThat(priorDisplayIdsWithSystemDecorations).containsExactly(0, 1)
+
+ val lastDisplayIdsWithSystemDecorations by
+ collectLastValue(displayRepository.displayIdsWithSystemDecorations)
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(0, 1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationRemoved_doesNotEmitRemovedDisplayId() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayRemoveSystemDecorations(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_systemDecorationsRemoved_nonExistentDisplay_noEffect() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayRemoveSystemDecorations(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_displayRemoved_doesNotEmitRemovedDisplayId() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayAddSystemDecorations(2)
+ sendOnDisplayRemoved(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_displayRemoved_nonExistentDisplay_noEffect() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ sendOnDisplayAddSystemDecorations(1)
+ sendOnDisplayRemoved(2)
+
+ assertThat(lastDisplayIdsWithSystemDecorations).containsExactly(1)
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_onFlowCollection_commandQueueCallbackRegistered() =
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(lastDisplayIdsWithSystemDecorations).isEmpty()
+
+ verify(commandQueue, times(1)).addCallback(any())
+ }
+
+ @Test
+ fun displayIdsWithSystemDecorations_afterFlowCollection_commandQueueCallbackUnregistered() {
+ testScope.runTest {
+ val lastDisplayIdsWithSystemDecorations by latestDisplayIdsWithSystemDecorationsValue()
+
+ assertThat(lastDisplayIdsWithSystemDecorations).isEmpty()
+
+ verify(commandQueue, times(1)).addCallback(any())
+ }
+ verify(commandQueue, times(1)).removeCallback(any())
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
private fun Iterable<Set<Display>>.toIdSets(): List<Set<Int>> = map { it.ids().toSet() }
@@ -550,6 +667,14 @@ class DisplayRepositoryTest : SysuiTestCase() {
return flowValue
}
+ // Wrapper to capture the displayListener and commandQueueCallbacks.
+ private fun TestScope.latestDisplayIdsWithSystemDecorationsValue(): FlowValue<Set<Int>?> {
+ val flowValue = collectLastValue(displayRepository.displayIdsWithSystemDecorations)
+ captureAddedRemovedListener()
+ captureCommandQueueCallbacks()
+ return flowValue
+ }
+
private fun captureAddedRemovedListener() {
verify(displayManager)
.registerDisplayListener(
@@ -563,6 +688,10 @@ class DisplayRepositoryTest : SysuiTestCase() {
)
}
+ private fun captureCommandQueueCallbacks() {
+ verify(commandQueue).addCallback(commandQueueCallbacks.capture())
+ }
+
private fun sendOnDisplayAdded(id: Int, displayType: Int) {
val mockDisplay = display(id = id, type = displayType)
whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay)
@@ -592,6 +721,14 @@ class DisplayRepositoryTest : SysuiTestCase() {
connectedDisplayListener.value.onDisplayChanged(id)
}
+ private fun sendOnDisplayRemoveSystemDecorations(id: Int) {
+ commandQueueCallbacks.value.onDisplayRemoveSystemDecorations(id)
+ }
+
+ private fun sendOnDisplayAddSystemDecorations(id: Int) {
+ commandQueueCallbacks.value.onDisplayAddSystemDecorations(id)
+ }
+
private fun setDisplays(displays: List<Display>) {
whenever(displayManager.displays).thenReturn(displays.toTypedArray())
displays.forEach { display ->
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 d4642006e68d..721d116004f3 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
@@ -26,14 +26,16 @@ import android.hardware.display.DisplayManager.EVENT_TYPE_DISPLAY_REMOVED
import android.os.Handler
import android.util.Log
import android.view.Display
+import android.view.IWindowManager
import com.android.app.tracing.FlowTracing.traceEach
import com.android.app.tracing.traceSection
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.DisplayEvent
+import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.Compile
import com.android.systemui.util.kotlin.pairwiseBy
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -43,6 +45,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -50,12 +53,13 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.stateIn
-/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
+/** Repository for providing access to display related information and events. */
interface DisplayRepository {
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
@@ -66,6 +70,9 @@ interface DisplayRepository {
/** Display removal event indicating a display has been removed. */
val displayRemovalEvent: Flow<Int>
+ /** A [StateFlow] that maintains a set of display IDs that should have system decorations. */
+ val displayIdsWithSystemDecorations: StateFlow<Set<Int>>
+
/**
* Provides the current set of displays.
*
@@ -124,6 +131,8 @@ class DisplayRepositoryImpl
@Inject
constructor(
private val displayManager: DisplayManager,
+ private val commandQueue: CommandQueue,
+ private val windowManager: IWindowManager,
@Background backgroundHandler: Handler,
@Background bgApplicationScope: CoroutineScope,
@Background backgroundCoroutineDispatcher: CoroutineDispatcher,
@@ -426,6 +435,56 @@ constructor(
.map { it.resultSet }
}
+ private val decorationEvents: Flow<Event> = callbackFlow {
+ val callback =
+ object : CommandQueue.Callbacks {
+ override fun onDisplayAddSystemDecorations(displayId: Int) {
+ trySend(Event.Add(displayId))
+ }
+
+ override fun onDisplayRemoveSystemDecorations(displayId: Int) {
+ trySend(Event.Remove(displayId))
+ }
+ }
+ commandQueue.addCallback(callback)
+ awaitClose { commandQueue.removeCallback(callback) }
+ }
+
+ private val initialDisplayIdsWithDecorations: Set<Int> =
+ displayIds.value.filter { windowManager.shouldShowSystemDecors(it) }.toSet()
+
+ /**
+ * A [StateFlow] that maintains a set of display IDs that should have system decorations.
+ *
+ * Updates to the set are triggered by:
+ * - Adding displays via [CommandQueue.Callbacks.onDisplayAddSystemDecorations].
+ * - Removing displays via [CommandQueue.Callbacks.onDisplayRemoveSystemDecorations].
+ * - Removing displays via [displayRemovalEvent] emissions.
+ *
+ * The set is initialized with displays that qualify for system decorations based on
+ * [WindowManager.shouldShowSystemDecors].
+ */
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> =
+ merge(decorationEvents, displayRemovalEvent.map { Event.Remove(it) })
+ .scan(initialDisplayIdsWithDecorations) { displayIds: Set<Int>, event: Event ->
+ when (event) {
+ is Event.Add -> displayIds + event.displayId
+ is Event.Remove -> displayIds - event.displayId
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(
+ scope = bgApplicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = initialDisplayIdsWithDecorations,
+ )
+
+ private sealed class Event(val displayId: Int) {
+ class Add(displayId: Int) : Event(displayId)
+
+ class Remove(displayId: Int) : Event(displayId)
+ }
+
private companion object {
const val TAG = "DisplayRepository"
val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
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 a64fc2413246..d6f0e06e104d 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
@@ -104,6 +104,8 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
+ override val displayIdsWithSystemDecorations: StateFlow<Set<Int>> = MutableStateFlow(emptySet())
+
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
fun setDefaultDisplayOff(defaultDisplayOff: Boolean) {