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

Commit b353e165 authored by Chris Göllner's avatar Chris Göllner Committed by Android (Google) Code Review
Browse files

Merge "Multi display initialization of Status Bar" into main

parents 7d0b9bc8 07274db5
Loading
Loading
Loading
Loading
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.statusbar.core

import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.verify

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
    @get:Rule val expect: Expect = Expect.create()

    private val kosmos =
        testKosmos().also {
            it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
            it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
        }
    private val testScope = kosmos.testScope
    private val fakeDisplayRepository = kosmos.displayRepository
    private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
    private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore

    // Lazy, so that @EnableFlags is set before initializer is instantiated.
    private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }

    @Test
    fun start_startsInitializersForCurrentDisplays() =
        testScope.runTest {
            fakeDisplayRepository.addDisplay(displayId = 1)
            fakeDisplayRepository.addDisplay(displayId = 2)

            underTest.start()
            runCurrent()

            expect
                .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable)
                .isTrue()
            expect
                .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable)
                .isTrue()
        }

    @Test
    fun start_startsOrchestratorForCurrentDisplays() =
        testScope.runTest {
            fakeDisplayRepository.addDisplay(displayId = 1)
            fakeDisplayRepository.addDisplay(displayId = 2)

            underTest.start()
            runCurrent()

            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start()
            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
        }

    @Test
    fun displayAdded_orchestratorForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()
            runCurrent()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
        }

    @Test
    fun displayAdded_initializerForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()
            runCurrent()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            expect
                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
                .isTrue()
        }

    @Test
    fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            expect
                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
                .isTrue()
        }

    @Test
    fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() =
        testScope.runTest {
            underTest.start()

            fakeDisplayRepository.addDisplay(displayId = 3)
            runCurrent()

            expect
                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
                .isTrue()
        }
}
+1 −2
Original line number Diff line number Diff line
@@ -60,10 +60,9 @@ class StatusBarInitializerTest : SysuiTestCase() {

    val underTest =
        StatusBarInitializerImpl(
            displayId = context.displayId,
            statusBarWindowControllerStore = windowControllerStore,
            collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) },
            creationListeners = setOf(),
            statusBarWindowController = windowController,
        )

    @Test
+39 −46
Original line number Diff line number Diff line
@@ -38,13 +38,10 @@ import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository
import com.android.systemui.statusbar.window.fakeStatusBarWindowController
import com.android.systemui.testKosmos
import com.android.wm.shell.bubbles.bubbles
import com.google.common.truth.Truth.assertThat
@@ -60,25 +57,20 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class StatusBarOrchestratorTest : SysuiTestCase() {

    private val kosmos =
        testKosmos().also {
            it.testDispatcher = it.unconfinedTestDispatcher
            it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore
        }
    private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher }
    private val testScope = kosmos.testScope
    private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
    private val statusBarWindowControllerStore = kosmos.fakeStatusBarWindowControllerStore
    private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
    private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
    private val notificationShadeWindowViewController =
    private val fakeStatusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
    private val mockPluginDependencyProvider = kosmos.mockPluginDependencyProvider
    private val mockNotificationShadeWindowViewController =
        kosmos.mockNotificationShadeWindowViewController
    private val shadeSurface = kosmos.mockShadeSurface
    private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
    private val fakeStatusBarWindowStateRepositoryStore =
        kosmos.fakeStatusBarWindowStateRepositoryStore
    private val mockShadeSurface = kosmos.mockShadeSurface
    private val fakeBouncerRepository = kosmos.fakeKeyguardBouncerRepository
    private val fakeStatusBarWindowStatePerDisplayRepository =
        kosmos.fakeStatusBarWindowStatePerDisplayRepository
    private val fakePowerRepository = kosmos.fakePowerRepository
    private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
    private val mockBubbles = kosmos.bubbles
    private val fakeStatusBarWindowController = kosmos.fakeStatusBarWindowController
    private val fakeStatusBarInitializer = kosmos.fakeStatusBarInitializer

    private val orchestrator = kosmos.statusBarOrchestrator

@@ -86,30 +78,31 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
    fun start_setsUpPluginDependencies() {
        orchestrator.start()

        verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
        verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java)
        verify(mockPluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
        verify(mockPluginDependencyProvider)
            .allowPluginDependency(StatusBarStateController::class.java)
    }

    @Test
    fun start_attachesWindow() {
        orchestrator.start()

        assertThat(statusBarWindowControllerStore.defaultDisplay.isAttached).isTrue()
        assertThat(fakeStatusBarWindowController.isAttached).isTrue()
    }

    @Test
    fun start_setsStatusBarControllerOnShade() {
        orchestrator.start()

        verify(notificationShadeWindowViewController)
            .setStatusBarViewController(statusBarViewController)
        verify(mockNotificationShadeWindowViewController)
            .setStatusBarViewController(fakeStatusBarInitializer.statusBarViewController)
    }

    @Test
    fun start_updatesShadeExpansion() {
        orchestrator.start()

        verify(shadeSurface).updateExpansionAndVisibility()
        verify(mockShadeSurface).updateExpansionAndVisibility()
    }

    @Test
@@ -117,9 +110,9 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
        testScope.runTest {
            orchestrator.start()

            bouncerRepository.setPrimaryShow(isShowing = true)
            fakeBouncerRepository.setPrimaryShow(isShowing = true)

            verify(statusBarViewController)
            verify(fakeStatusBarInitializer.statusBarViewController)
                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
        }

@@ -128,9 +121,9 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
        testScope.runTest {
            orchestrator.start()

            bouncerRepository.setPrimaryShow(isShowing = false)
            fakeBouncerRepository.setPrimaryShow(isShowing = false)

            verify(statusBarViewController)
            verify(fakeStatusBarInitializer.statusBarViewController)
                .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
        }

@@ -141,7 +134,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions).finishAnimations()
            verify(fakeStatusBarInitializer.statusBarTransitions).finishAnimations()
        }

    @Test
@@ -151,7 +144,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions, never()).finishAnimations()
            verify(fakeStatusBarInitializer.statusBarTransitions, never()).finishAnimations()
        }

    @Test
@@ -208,7 +201,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
        }

@@ -222,19 +215,19 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)

            setStatusBarMode(OPAQUE)
            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true)

            setStatusBarMode(LIGHTS_OUT)
            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true)

            setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
        }

@@ -248,7 +241,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
        }

@@ -262,7 +255,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
        }

@@ -276,7 +269,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {

            orchestrator.start()

            verify(mockPhoneStatusBarTransitions)
            verify(fakeStatusBarInitializer.statusBarTransitions)
                .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
        }

@@ -295,7 +288,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
            setTransientStatusBar()
            clearTransientStatusBar()

            verify(mockPhoneStatusBarTransitions, times(1))
            verify(fakeStatusBarInitializer.statusBarTransitions, times(1))
                .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
        }

@@ -318,18 +311,18 @@ class StatusBarOrchestratorTest : SysuiTestCase() {
    }

    private fun setTransientStatusBar() {
        statusBarModeRepository.defaultDisplay.showTransient()
        fakeStatusBarModePerDisplayRepository.showTransient()
    }

    private fun clearTransientStatusBar() {
        statusBarModeRepository.defaultDisplay.clearTransient()
        fakeStatusBarModePerDisplayRepository.clearTransient()
    }

    private fun setStatusBarWindowState(state: StatusBarWindowState) {
        fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state)
        fakeStatusBarWindowStatePerDisplayRepository.setWindowState(state)
    }

    private fun setStatusBarMode(statusBarMode: StatusBarMode) {
        statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode
        fakeStatusBarModePerDisplayRepository.statusBarMode.value = statusBarMode
    }
}
+92 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.statusbar.core

import android.view.Display
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
import com.android.systemui.util.kotlin.pairwiseBy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch

/**
 * Responsible for creating and starting the status bar components for each display. Also does it
 * for newly added displays.
 */
@SysUISingleton
class MultiDisplayStatusBarStarter
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val displayScopeRepository: DisplayScopeRepository,
    private val statusBarOrchestratorFactory: StatusBarOrchestrator.Factory,
    private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
    private val displayRepository: DisplayRepository,
    private val initializerStore: StatusBarInitializerStore,
    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
    private val statusBarInitializerStore: StatusBarInitializerStore,
) : CoreStartable {

    init {
        StatusBarConnectedDisplays.assertInNewMode()
    }

    override fun start() {
        applicationScope.launch {
            displayRepository.displays
                .pairwiseBy { previousDisplays, currentDisplays ->
                    currentDisplays - previousDisplays
                }
                .onStart { emit(displayRepository.displays.value) }
                .collect { newDisplays ->
                    newDisplays.forEach { createAndStartComponentsForDisplay(it) }
                }
        }
    }

    private fun createAndStartComponentsForDisplay(display: Display) {
        val displayId = display.displayId
        createAndStartOrchestratorForDisplay(displayId)
        createAndStartInitializerForDisplay(displayId)
    }

    private fun createAndStartOrchestratorForDisplay(displayId: Int) {
        statusBarOrchestratorFactory
            .create(
                displayId,
                displayScopeRepository.scopeForDisplay(displayId),
                statusBarWindowStateRepositoryStore.forDisplay(displayId),
                statusBarModeRepositoryStore.forDisplay(displayId),
                initializerStore.forDisplay(displayId),
                statusBarWindowControllerStore.forDisplay(displayId),
            )
            .start()
    }

    private fun createAndStartInitializerForDisplay(displayId: Int) {
        statusBarInitializerStore.forDisplay(displayId).start()
    }
}
+9 −8
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.StatusBarWindowController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -37,7 +37,7 @@ import javax.inject.Provider
 * Responsible for creating the status bar window and initializing the root components of that
 * window (see [CollapsedStatusBarFragment])
 */
interface StatusBarInitializer {
interface StatusBarInitializer : CoreStartable {

    var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener?

@@ -68,18 +68,17 @@ interface StatusBarInitializer {
    }

    interface Factory {
        fun create(displayId: Int): StatusBarInitializer
        fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
    }
}

class StatusBarInitializerImpl
@AssistedInject
constructor(
    @Assisted private val displayId: Int,
    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
    @Assisted private val statusBarWindowController: StatusBarWindowController,
    private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
    private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
) : CoreStartable, StatusBarInitializer {
) : StatusBarInitializer {
    private var component: StatusBarFragmentComponent? = null

    @get:VisibleForTesting
@@ -111,7 +110,7 @@ constructor(

    private fun doStart() {
        initialized = true
        statusBarWindowControllerStore.defaultDisplay.fragmentHostManager
        statusBarWindowController.fragmentHostManager
            .addTagListener(
                CollapsedStatusBarFragment.TAG,
                object : FragmentHostManager.FragmentListener {
@@ -145,6 +144,8 @@ constructor(

    @AssistedFactory
    interface Factory : StatusBarInitializer.Factory {
        override fun create(displayId: Int): StatusBarInitializerImpl
        override fun create(
            statusBarWindowController: StatusBarWindowController
        ): StatusBarInitializerImpl
    }
}
Loading