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

Commit e649b388 authored by amehfooz's avatar amehfooz Committed by Ahmed Mehfooz
Browse files

Add support for keeping status bar visible in immersive mode

This CL adds support in OngoingCallInteractor to keep
the status bar visible if there is an ongoing call
and an app is in full screen mode.
Also, broke up the OngoingCallState code into multiple
functions to improve readability.

Testing: Manually tested and added new tests.
Bug: b/372657935
Flag: com.android.systemui.status_bar_chips_modernization

Change-Id: I4ca9fa1f90da31d726c4dc76246c91eb48895422
parent be71881e
Loading
Loading
Loading
Loading
+127 −8
Original line number Diff line number Diff line
@@ -30,11 +30,13 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -72,7 +74,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                statusBarChipIcon = testIconView,
                                contentIntent = testIntent
                                contentIntent = testIntent,
                            )
                        )
                    }
@@ -95,7 +97,9 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                            )
                        )
                    }
@@ -114,7 +118,9 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "notif1", whenTime = 1000L, callType = CallType.Ongoing
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                            )
                        )
                    }
@@ -138,7 +144,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID
                                uid = UID,
                            )
                        )
                    }
@@ -161,7 +167,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID
                                uid = UID,
                            )
                        )
                    }
@@ -185,13 +191,12 @@ class OngoingCallInteractorTest : SysuiTestCase() {
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID
                                uid = UID,
                            )
                        )
                    }
                    .build()
            assertThat(latest)
                .isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)

            // App becomes visible
            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)
@@ -202,6 +207,120 @@ class OngoingCallInteractorTest : SysuiTestCase() {
            assertThat(latest).isInstanceOf(OngoingCallModel.InCall::class.java)
        }

    @Test
    fun ongoingCallNotification_setsRequiresStatusBarVisibleTrue() =
        kosmos.runTest {
            val ongoingCallState by collectLastValue(underTest.ongoingCallState)

            val requiresStatusBarVisibleInRepository by
                collectLastValue(
                    kosmos.fakeStatusBarModeRepository.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )
            val requiresStatusBarVisibleInWindowController by
                collectLastValue(
                    kosmos.fakeStatusBarWindowControllerStore.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )
            repository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID,
                            )
                        )
                    }
                    .build()

            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat(requiresStatusBarVisibleInRepository).isTrue()
            assertThat(requiresStatusBarVisibleInWindowController).isTrue()
        }

    @Test
    fun notificationRemoved_setsRequiresStatusBarVisibleFalse() =
        kosmos.runTest {
            val ongoingCallState by collectLastValue(underTest.ongoingCallState)

            val requiresStatusBarVisibleInRepository by
                collectLastValue(
                    kosmos.fakeStatusBarModeRepository.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )
            val requiresStatusBarVisibleInWindowController by
                collectLastValue(
                    kosmos.fakeStatusBarWindowControllerStore.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )

            repository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID,
                            )
                        )
                    }
                    .build()

            repository.activeNotifications.value = ActiveNotificationsStore()

            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.NoCall::class.java)
            assertThat(requiresStatusBarVisibleInRepository).isFalse()
            assertThat(requiresStatusBarVisibleInWindowController).isFalse()
        }

    @Test
    fun ongoingCallNotification_appBecomesVisible_setsRequiresStatusBarVisibleFalse() =
        kosmos.runTest {
            val ongoingCallState by collectLastValue(underTest.ongoingCallState)

            val requiresStatusBarVisibleInRepository by
                collectLastValue(
                    kosmos.fakeStatusBarModeRepository.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )
            val requiresStatusBarVisibleInWindowController by
                collectLastValue(
                    kosmos.fakeStatusBarWindowControllerStore.defaultDisplay
                        .ongoingProcessRequiresStatusBarVisible
                )

            kosmos.activityManagerRepository.fake.startingIsAppVisibleValue = false
            repository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "notif1",
                                whenTime = 1000L,
                                callType = CallType.Ongoing,
                                uid = UID,
                            )
                        )
                    }
                    .build()

            assertThat(ongoingCallState).isInstanceOf(OngoingCallModel.InCall::class.java)
            assertThat(requiresStatusBarVisibleInRepository).isTrue()
            assertThat(requiresStatusBarVisibleInWindowController).isTrue()

            kosmos.activityManagerRepository.fake.setIsAppVisible(UID, true)

            assertThat(ongoingCallState)
                .isInstanceOf(OngoingCallModel.InCallWithVisibleApp::class.java)
            assertThat(requiresStatusBarVisibleInRepository).isFalse()
            assertThat(requiresStatusBarVisibleInWindowController).isFalse()
        }

    companion object {
        private const val UID = 885
    }
+34 −2
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.phone.BoundsPair
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import dagger.assisted.Assisted
@@ -89,6 +90,9 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener
    /** The current mode of the status bar. */
    val statusBarMode: StateFlow<StatusBarMode>

    /** Whether the status bar is forced to be visible because of an ongoing call */
    val ongoingProcessRequiresStatusBarVisible: StateFlow<Boolean>

    /**
     * Requests for the status bar to be shown transiently.
     *
@@ -110,6 +114,12 @@ interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener
     * if needed.
     */
    fun stop()

    /**
     * Called when an ongoing process needs to prevent the status bar from being hidden in any
     * state.
     */
    fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean)
}

class StatusBarModePerDisplayRepositoryImpl
@@ -195,6 +205,16 @@ constructor(
        statusBarBoundsProvider.addChangeListener(listener)
    }

    private val _ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false)
    override val ongoingProcessRequiresStatusBarVisible =
        _ongoingProcessRequiresStatusBarVisible.asStateFlow()

    override fun setOngoingProcessRequiresStatusBarVisible(
        requiredVisible: Boolean
    ) {
        _ongoingProcessRequiresStatusBarVisible.value = requiredVisible
    }

    override val isInFullscreenMode: StateFlow<Boolean> =
        _originalStatusBarAttributes
            .map { params ->
@@ -235,16 +255,28 @@ constructor(
                isTransientShown,
                isInFullscreenMode,
                ongoingCallRepository.ongoingCallState,
            ) { modifiedAttributes, isTransientShown, isInFullscreenMode, ongoingCallState ->
                _ongoingProcessRequiresStatusBarVisible,
            ) {
                modifiedAttributes,
                isTransientShown,
                isInFullscreenMode,
                ongoingCallStateLegacy,
                ongoingProcessRequiresStatusBarVisible ->
                if (modifiedAttributes == null) {
                    null
                } else {
                    val hasOngoingCall =
                        if (StatusBarChipsModernization.isEnabled) {
                            ongoingProcessRequiresStatusBarVisible
                        } else {
                            ongoingCallStateLegacy is OngoingCallModel.InCall
                        }
                    val statusBarMode =
                        toBarMode(
                            modifiedAttributes.appearance,
                            isTransientShown,
                            isInFullscreenMode,
                            hasOngoingCall = ongoingCallState is OngoingCallModel.InCall,
                            hasOngoingCall,
                        )
                    StatusBarAppearance(
                        statusBarMode,
+76 −37
Original line number Diff line number Diff line
@@ -21,17 +21,22 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn

/**
@@ -47,7 +52,9 @@ import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class OngoingCallInteractor @Inject constructor(
    @Application private val scope: CoroutineScope,
    activityManagerRepository: ActivityManagerRepository,
    private val activityManagerRepository: ActivityManagerRepository,
    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
    activeNotificationsInteractor: ActiveNotificationsInteractor,
    @OngoingCallLog private val logBuffer: LogBuffer,
) {
@@ -58,22 +65,45 @@ class OngoingCallInteractor @Inject constructor(
     */
    val ongoingCallState: StateFlow<OngoingCallModel> =
        activeNotificationsInteractor.ongoingCallNotification
            .flatMapLatest { notificationModel ->
                when (notificationModel) {
                    null -> {
            .flatMapLatest { notification ->
                createOngoingCallStateFlow(
                    notification = notification
                )
            }
            .onEach { state ->
                setStatusBarRequiredForOngoingCall(state)
            }
            .stateIn(
                scope = scope,
                started = SharingStarted.WhileSubscribed(),
                initialValue = OngoingCallModel.NoCall
            )

    private fun createOngoingCallStateFlow(
        notification: ActiveNotificationModel?
    ): Flow<OngoingCallModel> {
        if (notification == null) {
            logger.d("No active call notification - hiding chip")
                        flowOf(OngoingCallModel.NoCall)
            return flowOf(OngoingCallModel.NoCall)
        }

                    else -> combine(
                        flowOf(notificationModel),
        return combine(
            flowOf(notification),
            activityManagerRepository.createIsAppVisibleFlow(
                            creationUid = notificationModel.uid,
                creationUid = notification.uid,
                logger = logger,
                identifyingLogTag = TAG,
                        ),
            )
        ) { model, isVisible ->
                        when {
            deriveOngoingCallState(model, isVisible)
        }
    }

    private fun deriveOngoingCallState(
        model: ActiveNotificationModel,
        isVisible: Boolean
    ): OngoingCallModel {
        return when {
            isVisible -> {
                logger.d({ "Call app is visible: uid=$int1" }) {
                    int1 = model.uid
@@ -90,14 +120,23 @@ class OngoingCallInteractor @Inject constructor(
                    startTimeMs = model.whenTime,
                    notificationIconView = model.statusBarChipIconView,
                    intent = model.contentIntent,
                                    notificationKey = model.key,
                    notificationKey = model.key
                )
            }
        }
    }

    private fun setStatusBarRequiredForOngoingCall(state: OngoingCallModel) {
        val statusBarRequired = state is OngoingCallModel.InCall
        // TODO(b/382808183): Create a single repository that can be utilized in
        //  `statusBarModeRepositoryStore` and `statusBarWindowControllerStore` so we do not need
        //  two separate calls to force the status bar to stay visible.
        statusBarModeRepositoryStore.defaultDisplay.setOngoingProcessRequiresStatusBarVisible(
            statusBarRequired
        )
        statusBarWindowControllerStore.defaultDisplay
            .setOngoingProcessRequiresStatusBarVisible(statusBarRequired)
    }
            }
            .stateIn(scope, SharingStarted.WhileSubscribed(), OngoingCallModel.NoCall)

    companion object {
        private val TAG = "OngoingCall"
+4 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository
    override val isInFullscreenMode = MutableStateFlow(false)
    override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
    override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
    override val ongoingProcessRequiresStatusBarVisible = MutableStateFlow(false)

    override fun showTransient() {
        isTransientShown.value = true
@@ -59,6 +60,9 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository
    override fun start() {}

    override fun stop() {}
    override fun setOngoingProcessRequiresStatusBarVisible(requiredVisible: Boolean) {
        ongoingProcessRequiresStatusBarVisible.value = requiredVisible
    }

    override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}

+4 −0
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@ import com.android.systemui.activity.data.repository.activityManagerRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore

val Kosmos.ongoingCallInteractor: OngoingCallInteractor by
    Kosmos.Fixture {
@@ -28,6 +30,8 @@ val Kosmos.ongoingCallInteractor: OngoingCallInteractor by
          scope = applicationCoroutineScope,
          activeNotificationsInteractor = activeNotificationsInteractor,
          activityManagerRepository = activityManagerRepository,
          statusBarModeRepositoryStore = fakeStatusBarModeRepository,
          statusBarWindowControllerStore = fakeStatusBarWindowControllerStore,
          logBuffer = logcatLogBuffer("OngoingCallInteractorKosmos"),
      )
    }
Loading