Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9eaf182b authored by alinazaidi's avatar alinazaidi
Browse files

Add flows for events for adding and removing status bar.

These flows utilize CommandQueue.Callbacks#onDisplayRemoveSystemDecorations and #onDisplayAddSystemDecorations

Test: atest com.android.systemui.display.data.repository.DisplayRepositoryTest
Bug: 384694270
Flag: com.android.systemui.shared.status_bar_connected_displays
Change-Id: Ic688ca15f42546632c0557be36fe7b6ae5a57047
parent 974b6631
Loading
Loading
Loading
Loading
+138 −1
Original line number Diff line number Diff line
@@ -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 ->
+61 −2
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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) {