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

Commit fd38757b authored by Caitlin Shkuratov's avatar Caitlin Shkuratov Committed by Android (Google) Code Review
Browse files

Merge changes Ib9685561,I3e3376d6 into main

* changes:
  [SB][Call] Hide call chip timer when chip is tapped.
  [SB][Call] Share click listener code between call chip and notif chip.
parents 9ef1e0c5 71ec7fa3
Loading
Loading
Loading
Loading
+186 −1
Original line number Diff line number Diff line
@@ -32,9 +32,12 @@ import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
@@ -46,7 +49,10 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
@@ -58,6 +64,7 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
@@ -74,7 +81,12 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
        mSetFlagsRule.setFlagsParameterization(flags)
    }

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val kosmos =
        testKosmos().useUnconfinedTestDispatcher().apply {
            // Don't be in lockscreen so that HUNs are allowed
            fakeKeyguardTransitionRepository =
                FakeKeyguardTransitionRepository(initInLockscreen = false, testScope = testScope)
        }

    private val chipBackgroundView = mock<ChipBackgroundContainer>()
    private val chipView =
@@ -190,6 +202,96 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            assertThat((latest as OngoingActivityChipModel.Active).instanceId).isEqualTo(instanceId)
        }

    @Test
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_noHun_chipHasTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(startTimeMs = 4_000)

            headsUpNotificationRepository.setNotifications(emptyList())

            assertThat((latest as OngoingActivityChipModel.Active).content)
                .isInstanceOf(OngoingActivityChipModel.Content.Timer::class.java)
        }

    @Test
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedBySystem_chipHasTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState()

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "systemNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).content)
                .isInstanceOf(OngoingActivityChipModel.Content.Timer::class.java)
        }

    @Test
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedByUser_forDifferentChip_chipHasTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(key = "thisNotif")

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "otherNotifPinnedByUser",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).content)
                .isInstanceOf(OngoingActivityChipModel.Content.Timer::class.java)
        }

    @Test
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedByUser_forThisChip_chipDoesNotHaveTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(key = "thisNotif")

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "thisNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).content)
                .isInstanceOf(OngoingActivityChipModel.Content.IconOnly::class.java)
        }

    @Test
    @DisableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedByUser_forThisChip_butPromotedFlagOff_chipHasTime() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(key = "thisNotif")

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "thisNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).content)
                .isInstanceOf(OngoingActivityChipModel.Content.Timer::class.java)
        }

    @Test
    @EnableChipsModernization
    fun chip_twoCallNotifs_earlierIsUsed() =
@@ -790,6 +892,89 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
            assertThat(latestChipTapKey).isEqualTo("fakeCallKey")
        }

    @Test
    @EnableChipsModernization
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_noHun_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState()

            headsUpNotificationRepository.setNotifications(emptyList())

            assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    @EnableChipsModernization
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedBySystem_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState()

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "systemNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedBySystem),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    @EnableChipsModernization
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedByUser_forDifferentChip_clickBehaviorIsShowHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(key = "thisNotif")

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "otherNotifPinnedByUser",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification::class.java
                )
        }

    @Test
    @EnableChipsModernization
    @EnableFlags(PromotedNotificationUi.FLAG_NAME)
    fun chip_inCall_hunPinnedByUser_forThisChip_clickBehaviorIsHideHun() =
        kosmos.runTest {
            val latest by collectLastValue(underTest.chip)

            addOngoingCallState(key = "thisNotif")

            headsUpNotificationRepository.setNotifications(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "thisNotif",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )

            assertThat((latest as OngoingActivityChipModel.Active).clickBehavior)
                .isInstanceOf(
                    OngoingActivityChipModel.ClickBehavior.HideHeadsUpNotification::class.java
                )
        }

    @Test
    @EnableFlags(StatusBarChipsReturnAnimations.FLAG_NAME)
    @EnableChipsModernization
+13 −0
Original line number Diff line number Diff line
@@ -19,6 +19,10 @@ package com.android.systemui.statusbar.chips.call.domain.interactor
import android.app.Flags
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.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
@@ -28,7 +32,9 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn

/** Interactor for the ongoing phone call chip shown in the status bar. */
@@ -39,7 +45,10 @@ constructor(
    @Application private val scope: CoroutineScope,
    ongoingCallInteractor: OngoingCallInteractor,
    repository: OngoingCallRepository,
    @StatusBarChipsLog private val logBuffer: LogBuffer,
) {
    private val logger = Logger(logBuffer, "CallChip".pad())

    val ongoingCallState: StateFlow<OngoingCallModel> =
        (if (StatusBarChipsModernization.isEnabled) {
                ongoingCallInteractor.ongoingCallState
@@ -60,5 +69,9 @@ constructor(
                    state
                }
            }
            .distinctUntilChanged()
            .onEach {
                logger.d({ "Call chip state updated: newState=$str1" }) { str1 = it.logString() }
            }
            .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
}
+72 −41
Original line number Diff line number Diff line
@@ -43,8 +43,13 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createNotificationToggleClickBehavior
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createNotificationToggleClickListenerLegacy
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.isShowingHeadsUpFromChipTap
import com.android.systemui.statusbar.chips.uievents.StatusBarChipsUiEventLogger
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.domain.model.TopPinnedState
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
@@ -57,10 +62,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/** View model for the ongoing phone call chip shown in the status bar. */
@SysUISingleton
@@ -71,12 +74,13 @@ constructor(
    @Application private val scope: CoroutineScope,
    interactor: CallChipInteractor,
    private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
    headsUpNotificationInteractor: HeadsUpNotificationInteractor,
    systemClock: SystemClock,
    private val activityStarter: ActivityStarter,
    @StatusBarChipsLog private val logBuffer: LogBuffer,
    private val uiEventLogger: StatusBarChipsUiEventLogger,
) : OngoingActivityChipViewModel {
    private val logger = Logger(logBuffer, "OngoingCallVM".pad())
    private val logger = Logger(logBuffer, "CallChipVM".pad())
    /** The transition cookie used to register and unregister launch and return animations. */
    private val cookie =
        ActivityTransitionAnimator.TransitionCookie("${CallChipViewModel::class.java}")
@@ -98,12 +102,17 @@ constructor(

    private val chipWithReturnAnimation: StateFlow<OngoingActivityChipModel> =
        if (StatusBarChipsReturnAnimations.isEnabled) {
            combine(interactor.ongoingCallState, transitionState) { newState, newTransitionState ->
            combine(
                    interactor.ongoingCallState,
                    transitionState,
                    headsUpNotificationInteractor.statusBarHeadsUpState,
                ) { newState, newTransitionState, headsUpState ->
                    val oldState = latestState
                    latestState = newState
                    val oldTransitionState = latestTransitionState
                    latestTransitionState = newTransitionState

                    // Note: This log might be too noisy with HUN transitions.
                    logger.d({
                        "Call chip state updated: $str1" +
                            " oldTransitionState=$str2" +
@@ -122,8 +131,9 @@ constructor(

                        is OngoingCallModel.InCall ->
                            prepareChip(
                                newState,
                                systemClock,
                                state = newState,
                                headsUpState = headsUpState,
                                systemClock = systemClock,
                                isHidden =
                                    shouldChipBeHidden(
                                        oldState = oldState,
@@ -146,19 +156,22 @@ constructor(

    private val chipLegacy: StateFlow<OngoingActivityChipModel> =
        if (!StatusBarChipsReturnAnimations.isEnabled) {
            interactor.ongoingCallState
                .map { state ->
                    logger.d({ "Call chip state updated: newState=$str1" }) {
                        str1 = state.logString()
                    }

                    when (state) {
            combine(
                    interactor.ongoingCallState,
                    headsUpNotificationInteractor.statusBarHeadsUpState,
                ) { callState, headsUpState ->
                    when (callState) {
                        is OngoingCallModel.NoCall -> OngoingActivityChipModel.Inactive()
                        is OngoingCallModel.InCall ->
                            if (state.isAppVisible) {
                            if (callState.isAppVisible) {
                                OngoingActivityChipModel.Inactive()
                            } else {
                                prepareChip(state, systemClock, isHidden = false)
                                prepareChip(
                                    state = callState,
                                    headsUpState = headsUpState,
                                    systemClock = systemClock,
                                    isHidden = false,
                                )
                            }
                    }
                }
@@ -187,6 +200,7 @@ constructor(
    /** Builds an [OngoingActivityChipModel] from all the relevant information. */
    private fun prepareChip(
        state: OngoingCallModel.InCall,
        headsUpState: TopPinnedState,
        systemClock: SystemClock,
        isHidden: Boolean,
        transitionState: TransitionState = TransitionState.NoTransition,
@@ -215,16 +229,28 @@ constructor(

        // This block mimics OngoingCallController#updateChip.
        val content =
            if (state.startTimeMs <= 0L) {
            when {
                state.startTimeMs <= 0L -> {
                    // If the start time is invalid, don't show a timer and show just an icon.
                    // See b/192379214.
                    OngoingActivityChipModel.Content.IconOnly
            } else {
                }
                PromotedNotificationUi.isEnabled &&
                    headsUpState.isShowingHeadsUpFromChipTap(
                        notificationKey = state.notificationKey
                    ) -> {
                    // If the user tapped this chip to show the HUN, we want to just show the icon
                    // because the HUN will show the rest of the information.
                    // Similar behavior to [NotifChipsViewModel].
                    OngoingActivityChipModel.Content.IconOnly
                }
                else -> {
                    val startTimeInElapsedRealtime =
                        state.startTimeMs - systemClock.currentTimeMillis() +
                            systemClock.elapsedRealtime()
                    OngoingActivityChipModel.Content.Timer(startTimeMs = startTimeInElapsedRealtime)
                }
            }

        return OngoingActivityChipModel.Active(
            key = key,
@@ -232,7 +258,13 @@ constructor(
            content = content,
            colors = colors,
            onClickListenerLegacy = getOnClickListener(intent, instanceId, state.notificationKey),
            clickBehavior = getClickBehavior(intent, instanceId, state.notificationKey),
            clickBehavior =
                getClickBehavior(
                    intent = intent,
                    instanceId = instanceId,
                    notificationKey = state.notificationKey,
                    headsUpState = headsUpState,
                ),
            isHidden = isHidden,
            transitionManager = getTransitionManager(state, transitionState),
            instanceId = instanceId,
@@ -245,11 +277,12 @@ constructor(
        notificationKey: String,
    ): View.OnClickListener? {
        if (PromotedNotificationUi.isEnabled) {
            return View.OnClickListener { view ->
                StatusBarChipsModernization.assertInLegacyMode()
                logChipTapped(instanceId)
                onCallChipTappedWithPromotionEnabled(notificationKey)
            }
            return createNotificationToggleClickListenerLegacy(
                applicationScope = scope,
                notifChipsInteractor = notifChipsInteractor,
                logger = logger,
                notificationKey = notificationKey,
            )
        }
        if (intent == null) {
            return null
@@ -276,13 +309,17 @@ constructor(
        intent: PendingIntent?,
        instanceId: InstanceId?,
        notificationKey: String,
        headsUpState: TopPinnedState,
    ): OngoingActivityChipModel.ClickBehavior {
        if (PromotedNotificationUi.isEnabled) {
            return OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification {
                StatusBarChipsModernization.unsafeAssertInNewMode()
                logChipTapped(instanceId)
                onCallChipTappedWithPromotionEnabled(notificationKey)
            }
            return createNotificationToggleClickBehavior(
                applicationScope = scope,
                notifChipsInteractor = notifChipsInteractor,
                logger = logger,
                notificationKey = notificationKey,
                isShowingHeadsUpFromChipTap =
                    headsUpState.isShowingHeadsUpFromChipTap(notificationKey),
            )
        }
        if (intent == null) {
            return OngoingActivityChipModel.ClickBehavior.None
@@ -320,12 +357,6 @@ constructor(
        uiEventLogger.logChipTapToShow(instanceId)
    }

    private fun onCallChipTappedWithPromotionEnabled(notificationKey: String) {
        // The notification pipeline needs everything to run on the main thread, so keep
        // this event on the main thread.
        scope.launch { notifChipsInteractor.onPromotedNotificationChipTapped(notificationKey) }
    }

    private fun getContentDescription(appName: String): ContentDescription {
        val ongoingCallDescription = context.getString(R.string.ongoing_call_content_description)
        return ContentDescription.Loaded(
+24 −32

File changed.

Preview size limit exceeded, changes collapsed.

+94 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading