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

Commit bfe4bc0c authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[SB][Call chip] Listen to ActiveNotificationsInteractor for call info." into main

parents 58098d7b b2e6859a
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -391,6 +391,17 @@ flag {
    bug: "332662551"
}

flag {
    name: "status_bar_use_repos_for_call_chip"
    namespace: "systemui"
    description: "Use repositories as the source of truth for call notifications shown as a chip in"
        "the status bar"
    bug: "328584859"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "compose_bouncer"
    namespace: "systemui"
+114 −0
Original line number Diff line number Diff line
@@ -24,8 +24,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.notification.collection.render.NotifStats
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.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +58,117 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
            assertThat(underTest.allNotificationsCountValue).isEqualTo(5)
        }

    @Test
    fun ongoingCallNotification_noCallNotifs_null() =
        testScope.runTest {
            val latest by collectLastValue(underTest.ongoingCallNotification)

            val normalNotifs =
                listOf(
                    activeNotificationModel(
                        key = "notif1",
                        callType = CallType.None,
                    ),
                    activeNotificationModel(
                        key = "notif2",
                        callType = CallType.None,
                    )
                )

            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply { normalNotifs.forEach(::addIndividualNotif) }
                    .build()

            assertThat(latest).isNull()
        }

    @Test
    fun ongoingCallNotification_incomingCallNotif_null() =
        testScope.runTest {
            val latest by collectLastValue(underTest.ongoingCallNotification)

            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "incomingNotif",
                                callType = CallType.Incoming,
                            )
                        )
                    }
                    .build()

            assertThat(latest).isNull()
        }

    @Test
    fun ongoingCallNotification_screeningCallNotif_null() =
        testScope.runTest {
            val latest by collectLastValue(underTest.ongoingCallNotification)

            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply {
                        addIndividualNotif(
                            activeNotificationModel(
                                key = "screeningNotif",
                                callType = CallType.Screening,
                            )
                        )
                    }
                    .build()

            assertThat(latest).isNull()
        }

    @Test
    fun ongoingCallNotification_ongoingCallNotif_hasNotif() =
        testScope.runTest {
            val latest by collectLastValue(underTest.ongoingCallNotification)

            val ongoingNotif =
                activeNotificationModel(
                    key = "ongoingNotif",
                    callType = CallType.Ongoing,
                )

            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply { addIndividualNotif(ongoingNotif) }
                    .build()

            assertThat(latest).isEqualTo(ongoingNotif)
        }

    @Test
    fun ongoingCallNotification_multipleCallNotifs_usesEarlierNotif() =
        testScope.runTest {
            val latest by collectLastValue(underTest.ongoingCallNotification)

            val earlierOngoingNotif =
                activeNotificationModel(
                    key = "earlierOngoingNotif",
                    callType = CallType.Ongoing,
                    whenTime = 45L,
                )
            val laterOngoingNotif =
                activeNotificationModel(
                    key = "laterOngoingNotif",
                    callType = CallType.Ongoing,
                    whenTime = 55L,
                )

            activeNotificationListRepository.activeNotifications.value =
                ActiveNotificationsStore.Builder()
                    .apply { addIndividualNotif(earlierOngoingNotif) }
                    .apply { addIndividualNotif(laterOngoingNotif) }
                    .build()

            assertThat(latest).isEqualTo(earlierOngoingNotif)
        }

    @Test
    fun areAnyNotificationsPresent_isTrue() =
        testScope.runTest {
+16 −0
Original line number Diff line number Diff line
@@ -15,11 +15,13 @@

package com.android.systemui.statusbar.notification.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -27,6 +29,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map

@SysUISingleton
class ActiveNotificationsInteractor
@Inject
constructor(
@@ -71,6 +74,19 @@ constructor(
    val allNotificationsCountValue: Int
        get() = repository.activeNotifications.value.individuals.size

    /**
     * The priority ongoing call notification, or null if there is no ongoing call.
     *
     * The output model is guaranteed to have [ActiveNotificationModel.callType] to be equal to
     * [CallType.Ongoing].
     */
    val ongoingCallNotification: Flow<ActiveNotificationModel?> =
        allRepresentativeNotifications.map { notifMap ->
            // Once a call has started, its `whenTime` should stay the same, so we can use it as a
            // stable sort value.
            notifMap.values.filter { it.callType == CallType.Ongoing }.minByOrNull { it.whenTime }
        }

    /** Are any notifications being actively presented in the notification stack? */
    val areAnyNotificationsPresent: Flow<Boolean> =
        repository.activeNotifications
+36 −0
Original line number Diff line number Diff line
@@ -15,7 +15,14 @@
 */
package com.android.systemui.statusbar.notification.domain.interactor

import android.app.Notification.CallStyle.CALL_TYPE_INCOMING
import android.app.Notification.CallStyle.CALL_TYPE_ONGOING
import android.app.Notification.CallStyle.CALL_TYPE_SCREENING
import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
import android.app.Notification.EXTRA_CALL_TYPE
import android.app.PendingIntent
import android.graphics.drawable.Icon
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -27,6 +34,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import javax.inject.Inject
import kotlinx.coroutines.flow.update

@@ -124,6 +132,7 @@ private class ActiveNotificationsStoreBuilder(
        existingModels.createOrReuse(
            key = key,
            groupKey = sbn.groupKey,
            whenTime = sbn.notification.`when`,
            isAmbient = sectionStyleProvider.isMinimized(this),
            isRowDismissed = isRowDismissed,
            isSilent = sectionStyleProvider.isSilent(this),
@@ -135,15 +144,18 @@ private class ActiveNotificationsStoreBuilder(
            statusBarIcon = icons.statusBarIcon?.sourceIcon,
            uid = sbn.uid,
            packageName = sbn.packageName,
            contentIntent = sbn.notification.contentIntent,
            instanceId = sbn.instanceId?.id,
            isGroupSummary = sbn.notification.isGroupSummary,
            bucket = bucket,
            callType = sbn.toCallType(),
        )
}

private fun ActiveNotificationsStore.createOrReuse(
    key: String,
    groupKey: String?,
    whenTime: Long,
    isAmbient: Boolean,
    isRowDismissed: Boolean,
    isSilent: Boolean,
@@ -155,14 +167,17 @@ private fun ActiveNotificationsStore.createOrReuse(
    statusBarIcon: Icon?,
    uid: Int,
    packageName: String,
    contentIntent: PendingIntent?,
    instanceId: Int?,
    isGroupSummary: Boolean,
    bucket: Int,
    callType: CallType,
): ActiveNotificationModel {
    return individuals[key]?.takeIf {
        it.isCurrent(
            key = key,
            groupKey = groupKey,
            whenTime = whenTime,
            isAmbient = isAmbient,
            isRowDismissed = isRowDismissed,
            isSilent = isSilent,
@@ -176,12 +191,15 @@ private fun ActiveNotificationsStore.createOrReuse(
            instanceId = instanceId,
            isGroupSummary = isGroupSummary,
            packageName = packageName,
            contentIntent = contentIntent,
            bucket = bucket,
            callType = callType,
        )
    }
        ?: ActiveNotificationModel(
            key = key,
            groupKey = groupKey,
            whenTime = whenTime,
            isAmbient = isAmbient,
            isRowDismissed = isRowDismissed,
            isSilent = isSilent,
@@ -195,13 +213,16 @@ private fun ActiveNotificationsStore.createOrReuse(
            instanceId = instanceId,
            isGroupSummary = isGroupSummary,
            packageName = packageName,
            contentIntent = contentIntent,
            bucket = bucket,
            callType = callType,
        )
}

private fun ActiveNotificationModel.isCurrent(
    key: String,
    groupKey: String?,
    whenTime: Long,
    isAmbient: Boolean,
    isRowDismissed: Boolean,
    isSilent: Boolean,
@@ -213,13 +234,16 @@ private fun ActiveNotificationModel.isCurrent(
    statusBarIcon: Icon?,
    uid: Int,
    packageName: String,
    contentIntent: PendingIntent?,
    instanceId: Int?,
    isGroupSummary: Boolean,
    bucket: Int,
    callType: CallType,
): Boolean {
    return when {
        key != this.key -> false
        groupKey != this.groupKey -> false
        whenTime != this.whenTime -> false
        isAmbient != this.isAmbient -> false
        isRowDismissed != this.isRowDismissed -> false
        isSilent != this.isSilent -> false
@@ -233,7 +257,9 @@ private fun ActiveNotificationModel.isCurrent(
        instanceId != this.instanceId -> false
        isGroupSummary != this.isGroupSummary -> false
        packageName != this.packageName -> false
        contentIntent != this.contentIntent -> false
        bucket != this.bucket -> false
        callType != this.callType -> false
        else -> true
    }
}
@@ -259,3 +285,13 @@ private fun ActiveNotificationGroupModel.isCurrent(
        else -> true
    }
}

private fun StatusBarNotification.toCallType(): CallType =
    when (this.notification.extras.getInt(EXTRA_CALL_TYPE, -1)) {
        -1 -> CallType.None
        CALL_TYPE_INCOMING -> CallType.Incoming
        CALL_TYPE_ONGOING -> CallType.Ongoing
        CALL_TYPE_SCREENING -> CallType.Screening
        CALL_TYPE_UNKNOWN -> CallType.Unknown
        else -> CallType.Unknown
    }
+21 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@

package com.android.systemui.statusbar.notification.shared

import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.notification.stack.PriorityBucket

@@ -32,6 +33,8 @@ data class ActiveNotificationModel(
    val key: String,
    /** Notification group key associated with this entry. */
    val groupKey: String?,
    /** When this notification was posted. */
    val whenTime: Long,
    /** Is this entry in the ambient / minimized section (lowest priority)? */
    val isAmbient: Boolean,
    /**
@@ -60,12 +63,16 @@ data class ActiveNotificationModel(
    val uid: Int,
    /** The notifying app's packageName. */
    val packageName: String,
    /** The intent to execute if UI related to this notification is clicked. */
    val contentIntent: PendingIntent?,
    /** A small per-notification ID, used for statsd logging. */
    val instanceId: Int?,
    /** If this notification is the group summary for a group of notifications. */
    val isGroupSummary: Boolean,
    /** Indicates in which section the notification is displayed in. @see [PriorityBucket]. */
    @PriorityBucket val bucket: Int,
    /** The call type set on the notification. */
    val callType: CallType,
) : ActiveNotificationEntryModel()

/** Model for a group of notifications. */
@@ -74,3 +81,17 @@ data class ActiveNotificationGroupModel(
    val summary: ActiveNotificationModel,
    val children: List<ActiveNotificationModel>,
) : ActiveNotificationEntryModel()

/** Specifies the call type set on the notification. For most notifications, will be [None]. */
enum class CallType {
    /** This notification isn't a call-type notification. */
    None,
    /** See [android.app.Notification.CallStyle.CALL_TYPE_INCOMING]. */
    Incoming,
    /** See [android.app.Notification.CallStyle.CALL_TYPE_ONGOING]. */
    Ongoing,
    /** See [android.app.Notification.CallStyle.CALL_TYPE_SCREENING]. */
    Screening,
    /** See [android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN]. */
    Unknown,
}
Loading