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

Commit 4204387b authored by Alina Zaidi's avatar Alina Zaidi Committed by Android (Google) Code Review
Browse files

Merge "Add flows for events for adding and removing status bar." into main

parents 93fcd2b5 9eaf182b
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) {