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

Commit c7bd6625 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Call] Share click listener code between call chip and notif chip.

Now that the call chip and the notification chips have the same click
behavior (show the associated notification), we should have them share
code.

This is mostly a no-op change, except that it makes the call chip click
listener correctly be `HideHeadsUpNotification` if the call notification
is already showing.

Bug: 414830065
Flag: android.app.ui_rich_ongoing
Test: atest NotifChipsViewModelTest CallChipViewModelTest
Test: Verify a11y content description of call chip changes based on HUN
status
Test: Verify chip clicks are still in log buffers
Change-Id: I360403a67a3caa8cc5278716f6b805371d0d7aae

Change-Id: I3e3376d6faae85fc26c443758e4e6b7567d127e0
parent 2b81818d
Loading
Loading
Loading
Loading
+96 −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 =
@@ -790,6 +802,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)
}
+53 −32
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,
@@ -214,6 +228,8 @@ constructor(
        val instanceId = state.notificationInstanceId

        // This block mimics OngoingCallController#updateChip.
        // TODO(b/414830065): If the call chip was tapped to show the notification (when
        // PromotedNotificationUi is enabled), don't show the time and show IconOnly instead.
        val content =
            if (state.startTimeMs <= 0L) {
                // If the start time is invalid, don't show a timer and show just an icon.
@@ -232,7 +248,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 +267,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 +299,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 +347,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(
+23 −32
Original line number Diff line number Diff line
@@ -17,26 +17,30 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel

import android.content.Context
import android.view.View
import com.android.internal.logging.InstanceId
import com.android.systemui.Flags
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
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.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.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.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +48,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

/** A view model for status bar chips for promoted ongoing notifications. */
@SysUISingleton
@@ -56,7 +59,9 @@ constructor(
    private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
    headsUpNotificationInteractor: HeadsUpNotificationInteractor,
    private val systemClock: SystemClock,
    @StatusBarChipsLog private val logBuffer: LogBuffer,
) {
    private val logger = Logger(logBuffer, "NotifChipVM".pad())

    /**
     * A flow that prunes the incoming [NotificationChipModel] instances to just the information
@@ -150,41 +155,27 @@ constructor(
                )
            }
        val colors = ColorsModel.SystemThemed
        val clickListener: () -> Unit = {
            // The notification pipeline needs everything to run on the main thread, so keep
            // this event on the main thread.
            applicationScope.launch {
                // TODO(b/364653005): Move accessibility focus to the HUN when chip is tapped.
                notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
            }
        }
        // If the app that posted this notification is visible, we want to hide the chip
        // because information between the status bar chip and the app itself could be
        // out-of-sync (like a timer that's slightly off)
        val isHidden = this.isAppVisible

        val isShowingHeadsUpFromChipTap =
            headsUpState is TopPinnedState.Pinned &&
                headsUpState.status == PinnedStatus.PinnedByUser &&
                headsUpState.key == this.key

        val isShowingHeadsUpFromChipTap = headsUpState.isShowingHeadsUpFromChipTap(this.key)
        val onClickListenerLegacy =
            View.OnClickListener {
                StatusBarChipsModernization.assertInLegacyMode()
                clickListener.invoke()
            }
            createNotificationToggleClickListenerLegacy(
                applicationScope = applicationScope,
                notifChipsInteractor = notifChipsInteractor,
                logger = logger,
                notificationKey = this.key,
            )
        val clickBehavior =
            if (isShowingHeadsUpFromChipTap) {
                OngoingActivityChipModel.ClickBehavior.HideHeadsUpNotification({
                    /* check if */ StatusBarChipsModernization.isUnexpectedlyInLegacyMode()
                    clickListener.invoke()
                })
            } else {
                OngoingActivityChipModel.ClickBehavior.ShowHeadsUpNotification({
                    /* check if */ StatusBarChipsModernization.isUnexpectedlyInLegacyMode()
                    clickListener.invoke()
                })
            }
            createNotificationToggleClickBehavior(
                applicationScope = applicationScope,
                notifChipsInteractor = notifChipsInteractor,
                logger = logger,
                notificationKey = this.key,
                isShowingHeadsUpFromChipTap = isShowingHeadsUpFromChipTap,
            )

        val content: OngoingActivityChipModel.Content =
            when {
+94 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading