Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +91 −0 Original line number Original line Diff line number Diff line Loading @@ -288,6 +288,97 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertIsNotifKey(latest!![1], secondKey) assertIsNotifKey(latest!![1], secondKey) } } @Test fun chips_notifTimeAndSystemTimeBothUpdated_modelNotRecreated() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val currentTime = 3.minutes.inWholeMilliseconds fakeSystemClock.setCurrentTimeMillis(currentTime) val oldPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.time = When.Time(currentTime) } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = oldPromotedContentBuilder.build(), ) ) ) assertThat(latest).hasSize(1) assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active::class.java) val oldModel = latest!![0] // WHEN the system time advances and the promoted content updates to that new time also val newTime = currentTime + 2.minutes.inWholeMilliseconds fakeSystemClock.setCurrentTimeMillis(newTime) val newPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.time = When.Time(newTime) } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = newPromotedContentBuilder.build(), ) ) ) // THEN we don't re-create the model because we still won't show the time assertThat(latest).hasSize(1) assertThat(latest!![0]).isSameInstanceAs(oldModel) } @Test fun chips_irrelevantPromotedContentUpdated_modelNotRecreated() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val oldPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.subText = "Old subtext" } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = oldPromotedContentBuilder.build(), ) ) ) assertThat(latest).hasSize(1) assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active::class.java) val oldModel = latest!![0] // WHEN promoted content updates with an irrelevant field val newPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.subText = "New subtext" } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = newPromotedContentBuilder.build(), ) ) ) // THEN we don't re-create the model assertThat(latest).hasSize(1) assertThat(latest!![0]).isSameInstanceAs(oldModel) } @Test @Test fun chips_appStartsAsVisible_isHiddenTrue() = fun chips_appStartsAsVisible_isHiddenTrue() = kosmos.runTest { kosmos.runTest { Loading packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +109 −57 Original line number Original line Diff line number Diff line Loading @@ -18,12 +18,14 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import android.content.Context import android.content.Context import android.view.View import android.view.View import com.android.internal.logging.InstanceId import com.android.systemui.Flags import com.android.systemui.Flags import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor 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.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel Loading @@ -41,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.launch /** A view model for status bar chips for promoted ongoing notifications. */ /** A view model for status bar chips for promoted ongoing notifications. */ Loading @@ -54,13 +57,62 @@ constructor( headsUpNotificationInteractor: HeadsUpNotificationInteractor, headsUpNotificationInteractor: HeadsUpNotificationInteractor, private val systemClock: SystemClock, private val systemClock: SystemClock, ) { ) { /** * A flow that prunes the incoming [NotificationChipModel] instances to just the information * each status bar chip needs. */ private val notificationChipsWithPrunedContent: Flow<List<PrunedNotificationChipModel>> = notifChipsInteractor.allNotificationChips .map { chips -> chips.map { it.toPrunedModel() } } .distinctUntilChanged() private fun NotificationChipModel.toPrunedModel(): PrunedNotificationChipModel { // Chips are never shown when locked, so it's safe to use the version with sensitive content val content = promotedContent.privateVersion val time = when (val rawTime = content.time) { null -> null is PromotedNotificationContentModel.When.Time -> { if ( rawTime.currentTimeMillis >= systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS ) { rawTime } else { // Don't show a `when` time that's close to now or in the past because it's // likely that the app didn't intentionally set the `when` time to be shown // in the status bar chip. // TODO(b/393369213): If a notification sets a `when` time in the future and // then that time comes and goes, the chip *will* start showing times in the // past. Not going to fix this right now because the Compose implementation // automatically handles this for us and we're hoping to launch the // notification chips at the same time as the Compose chips. null } } is PromotedNotificationContentModel.When.Chronometer -> rawTime } return PrunedNotificationChipModel( key = key, appName = appName, statusBarChipIconView = statusBarChipIconView, text = content.shortCriticalText, time = time, wasPromotedAutomatically = content.wasPromotedAutomatically, isAppVisible = isAppVisible, instanceId = instanceId, ) } /** /** * A flow modeling the current notification chips. Emits an empty list if there are no * A flow modeling the current notification chips. Emits an empty list if there are no * notifications that are eligible to show a status bar chip. * notifications that are eligible to show a status bar chip. */ */ val chips: Flow<List<OngoingActivityChipModel.Active>> = val chips: Flow<List<OngoingActivityChipModel.Active>> = combine( combine( notifChipsInteractor.allNotificationChips, notificationChipsWithPrunedContent, headsUpNotificationInteractor.statusBarHeadsUpState, headsUpNotificationInteractor.statusBarHeadsUpState, ) { notifications, headsUpState -> ) { notifications, headsUpState -> notifications.map { it.toActivityChipModel(headsUpState) } notifications.map { it.toActivityChipModel(headsUpState) } Loading @@ -68,12 +120,11 @@ constructor( .distinctUntilChanged() .distinctUntilChanged() /** Converts the notification to the [OngoingActivityChipModel] object. */ /** Converts the notification to the [OngoingActivityChipModel] object. */ private fun NotificationChipModel.toActivityChipModel( private fun PrunedNotificationChipModel.toActivityChipModel( headsUpState: TopPinnedState headsUpState: TopPinnedState ): OngoingActivityChipModel.Active { ): OngoingActivityChipModel.Active { PromotedNotificationUi.unsafeAssertInNewMode() PromotedNotificationUi.unsafeAssertInNewMode() // Chips are never shown when locked, so it's safe to use the version with sensitive content val chipContent = promotedContent.privateVersion val contentDescription = getContentDescription(this.appName) val contentDescription = getContentDescription(this.appName) val icon = val icon = if (this.statusBarChipIconView != null) { if (this.statusBarChipIconView != null) { Loading Loading @@ -131,12 +182,12 @@ constructor( ) ) } } if (chipContent.shortCriticalText != null) { if (text != null) { return OngoingActivityChipModel.Active.Text( return OngoingActivityChipModel.Active.Text( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, text = chipContent.shortCriticalText, text = text, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, Loading @@ -144,11 +195,11 @@ constructor( ) ) } } if (Flags.promoteNotificationsAutomatically() && chipContent.wasPromotedAutomatically) { if (Flags.promoteNotificationsAutomatically() && wasPromotedAutomatically) { // When we're promoting notifications automatically, the `when` time set on the // When we're promoting notifications automatically, the `when` time set on the // notification will likely just be set to the current time, which would cause the chip // notification will likely just be set to the current time, which would cause the chip // to always show "now". We don't want early testers to get that experience since it's // to always show "now". We don't want early testers to get that experience since it's // not what will happen at launch, so just don't show any time.onometerstate // not what will happen at launch, so just don't show any time. return OngoingActivityChipModel.Active.IconOnly( return OngoingActivityChipModel.Active.IconOnly( key = this.key, key = this.key, icon = icon, icon = icon, Loading @@ -160,8 +211,9 @@ constructor( ) ) } } if (chipContent.time == null) { return when (time) { return OngoingActivityChipModel.Active.IconOnly( null -> { OngoingActivityChipModel.Active.IconOnly( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, Loading @@ -171,50 +223,25 @@ constructor( instanceId = instanceId, instanceId = instanceId, ) ) } } when (chipContent.time) { is PromotedNotificationContentModel.When.Time -> { is PromotedNotificationContentModel.When.Time -> { return if ( chipContent.time.currentTimeMillis >= systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS ) { OngoingActivityChipModel.Active.ShortTimeDelta( OngoingActivityChipModel.Active.ShortTimeDelta( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, time = chipContent.time.currentTimeMillis, time = time.currentTimeMillis, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, instanceId = instanceId, instanceId = instanceId, ) ) } else { // Don't show a `when` time that's close to now or in the past because it's // likely that the app didn't intentionally set the `when` time to be shown in // the status bar chip. // TODO(b/393369213): If a notification sets a `when` time in the future and // then that time comes and goes, the chip *will* start showing times in the // past. Not going to fix this right now because the Compose implementation // automatically handles this for us and we're hoping to launch the notification // chips at the same time as the Compose chips. return OngoingActivityChipModel.Active.IconOnly( key = this.key, icon = icon, colors = colors, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, isHidden = isHidden, instanceId = instanceId, ) } } } is PromotedNotificationContentModel.When.Chronometer -> { is PromotedNotificationContentModel.When.Chronometer -> { return OngoingActivityChipModel.Active.Timer( OngoingActivityChipModel.Active.Timer( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, startTimeMs = chipContent.time.elapsedRealtimeMillis, startTimeMs = time.elapsedRealtimeMillis, isEventInFuture = chipContent.time.isCountDown, isEventInFuture = time.isCountDown, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, Loading @@ -236,6 +263,31 @@ constructor( ) ) } } /** * Model that prunes data from [NotificationChipModel] to just the information the status bar * chip needs. * * Used so that we don't re-create the chip [OngoingActivityChipModel] classes with new click * listeners unless absolutely necessary, which helps the chips re-compose less frequently. See * b/393456147. */ private data class PrunedNotificationChipModel( val key: String, val appName: String, val statusBarChipIconView: StatusBarIconView?, /** * The text to show in the chip, or null if text shouldn't be shown. Text takes precedence * over [time]. */ val text: String?, /** The time to show in the chip, or null if the time shouldn't be shown. */ val time: PromotedNotificationContentModel.When?, /** See [PromotedNotificationContentModel.wasPromotedAutomatically]. */ val wasPromotedAutomatically: Boolean, val isAppVisible: Boolean, val instanceId: InstanceId?, ) companion object { companion object { /** /** * Notifications must have a `when` time of at least 1 minute in the future in order for the * Notifications must have a `when` time of at least 1 minute in the future in order for the Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +91 −0 Original line number Original line Diff line number Diff line Loading @@ -288,6 +288,97 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertIsNotifKey(latest!![1], secondKey) assertIsNotifKey(latest!![1], secondKey) } } @Test fun chips_notifTimeAndSystemTimeBothUpdated_modelNotRecreated() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val currentTime = 3.minutes.inWholeMilliseconds fakeSystemClock.setCurrentTimeMillis(currentTime) val oldPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.time = When.Time(currentTime) } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = oldPromotedContentBuilder.build(), ) ) ) assertThat(latest).hasSize(1) assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active::class.java) val oldModel = latest!![0] // WHEN the system time advances and the promoted content updates to that new time also val newTime = currentTime + 2.minutes.inWholeMilliseconds fakeSystemClock.setCurrentTimeMillis(newTime) val newPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.time = When.Time(newTime) } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = newPromotedContentBuilder.build(), ) ) ) // THEN we don't re-create the model because we still won't show the time assertThat(latest).hasSize(1) assertThat(latest!![0]).isSameInstanceAs(oldModel) } @Test fun chips_irrelevantPromotedContentUpdated_modelNotRecreated() = kosmos.runTest { val latest by collectLastValue(underTest.chips) val oldPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.subText = "Old subtext" } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = oldPromotedContentBuilder.build(), ) ) ) assertThat(latest).hasSize(1) assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active::class.java) val oldModel = latest!![0] // WHEN promoted content updates with an irrelevant field val newPromotedContentBuilder = PromotedNotificationContentBuilder("notif").applyToShared { this.subText = "New subtext" } setNotifs( listOf( activeNotificationModel( key = "notif", statusBarChipIcon = createStatusBarIconViewOrNull(), promotedContent = newPromotedContentBuilder.build(), ) ) ) // THEN we don't re-create the model assertThat(latest).hasSize(1) assertThat(latest!![0]).isSameInstanceAs(oldModel) } @Test @Test fun chips_appStartsAsVisible_isHiddenTrue() = fun chips_appStartsAsVisible_isHiddenTrue() = kosmos.runTest { kosmos.runTest { Loading
packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +109 −57 Original line number Original line Diff line number Diff line Loading @@ -18,12 +18,14 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel import android.content.Context import android.content.Context import android.view.View import android.view.View import com.android.internal.logging.InstanceId import com.android.systemui.Flags import com.android.systemui.Flags import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor 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.notification.domain.model.NotificationChipModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.ColorsModel Loading @@ -41,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.launch /** A view model for status bar chips for promoted ongoing notifications. */ /** A view model for status bar chips for promoted ongoing notifications. */ Loading @@ -54,13 +57,62 @@ constructor( headsUpNotificationInteractor: HeadsUpNotificationInteractor, headsUpNotificationInteractor: HeadsUpNotificationInteractor, private val systemClock: SystemClock, private val systemClock: SystemClock, ) { ) { /** * A flow that prunes the incoming [NotificationChipModel] instances to just the information * each status bar chip needs. */ private val notificationChipsWithPrunedContent: Flow<List<PrunedNotificationChipModel>> = notifChipsInteractor.allNotificationChips .map { chips -> chips.map { it.toPrunedModel() } } .distinctUntilChanged() private fun NotificationChipModel.toPrunedModel(): PrunedNotificationChipModel { // Chips are never shown when locked, so it's safe to use the version with sensitive content val content = promotedContent.privateVersion val time = when (val rawTime = content.time) { null -> null is PromotedNotificationContentModel.When.Time -> { if ( rawTime.currentTimeMillis >= systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS ) { rawTime } else { // Don't show a `when` time that's close to now or in the past because it's // likely that the app didn't intentionally set the `when` time to be shown // in the status bar chip. // TODO(b/393369213): If a notification sets a `when` time in the future and // then that time comes and goes, the chip *will* start showing times in the // past. Not going to fix this right now because the Compose implementation // automatically handles this for us and we're hoping to launch the // notification chips at the same time as the Compose chips. null } } is PromotedNotificationContentModel.When.Chronometer -> rawTime } return PrunedNotificationChipModel( key = key, appName = appName, statusBarChipIconView = statusBarChipIconView, text = content.shortCriticalText, time = time, wasPromotedAutomatically = content.wasPromotedAutomatically, isAppVisible = isAppVisible, instanceId = instanceId, ) } /** /** * A flow modeling the current notification chips. Emits an empty list if there are no * A flow modeling the current notification chips. Emits an empty list if there are no * notifications that are eligible to show a status bar chip. * notifications that are eligible to show a status bar chip. */ */ val chips: Flow<List<OngoingActivityChipModel.Active>> = val chips: Flow<List<OngoingActivityChipModel.Active>> = combine( combine( notifChipsInteractor.allNotificationChips, notificationChipsWithPrunedContent, headsUpNotificationInteractor.statusBarHeadsUpState, headsUpNotificationInteractor.statusBarHeadsUpState, ) { notifications, headsUpState -> ) { notifications, headsUpState -> notifications.map { it.toActivityChipModel(headsUpState) } notifications.map { it.toActivityChipModel(headsUpState) } Loading @@ -68,12 +120,11 @@ constructor( .distinctUntilChanged() .distinctUntilChanged() /** Converts the notification to the [OngoingActivityChipModel] object. */ /** Converts the notification to the [OngoingActivityChipModel] object. */ private fun NotificationChipModel.toActivityChipModel( private fun PrunedNotificationChipModel.toActivityChipModel( headsUpState: TopPinnedState headsUpState: TopPinnedState ): OngoingActivityChipModel.Active { ): OngoingActivityChipModel.Active { PromotedNotificationUi.unsafeAssertInNewMode() PromotedNotificationUi.unsafeAssertInNewMode() // Chips are never shown when locked, so it's safe to use the version with sensitive content val chipContent = promotedContent.privateVersion val contentDescription = getContentDescription(this.appName) val contentDescription = getContentDescription(this.appName) val icon = val icon = if (this.statusBarChipIconView != null) { if (this.statusBarChipIconView != null) { Loading Loading @@ -131,12 +182,12 @@ constructor( ) ) } } if (chipContent.shortCriticalText != null) { if (text != null) { return OngoingActivityChipModel.Active.Text( return OngoingActivityChipModel.Active.Text( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, text = chipContent.shortCriticalText, text = text, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, Loading @@ -144,11 +195,11 @@ constructor( ) ) } } if (Flags.promoteNotificationsAutomatically() && chipContent.wasPromotedAutomatically) { if (Flags.promoteNotificationsAutomatically() && wasPromotedAutomatically) { // When we're promoting notifications automatically, the `when` time set on the // When we're promoting notifications automatically, the `when` time set on the // notification will likely just be set to the current time, which would cause the chip // notification will likely just be set to the current time, which would cause the chip // to always show "now". We don't want early testers to get that experience since it's // to always show "now". We don't want early testers to get that experience since it's // not what will happen at launch, so just don't show any time.onometerstate // not what will happen at launch, so just don't show any time. return OngoingActivityChipModel.Active.IconOnly( return OngoingActivityChipModel.Active.IconOnly( key = this.key, key = this.key, icon = icon, icon = icon, Loading @@ -160,8 +211,9 @@ constructor( ) ) } } if (chipContent.time == null) { return when (time) { return OngoingActivityChipModel.Active.IconOnly( null -> { OngoingActivityChipModel.Active.IconOnly( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, Loading @@ -171,50 +223,25 @@ constructor( instanceId = instanceId, instanceId = instanceId, ) ) } } when (chipContent.time) { is PromotedNotificationContentModel.When.Time -> { is PromotedNotificationContentModel.When.Time -> { return if ( chipContent.time.currentTimeMillis >= systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS ) { OngoingActivityChipModel.Active.ShortTimeDelta( OngoingActivityChipModel.Active.ShortTimeDelta( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, time = chipContent.time.currentTimeMillis, time = time.currentTimeMillis, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, instanceId = instanceId, instanceId = instanceId, ) ) } else { // Don't show a `when` time that's close to now or in the past because it's // likely that the app didn't intentionally set the `when` time to be shown in // the status bar chip. // TODO(b/393369213): If a notification sets a `when` time in the future and // then that time comes and goes, the chip *will* start showing times in the // past. Not going to fix this right now because the Compose implementation // automatically handles this for us and we're hoping to launch the notification // chips at the same time as the Compose chips. return OngoingActivityChipModel.Active.IconOnly( key = this.key, icon = icon, colors = colors, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, isHidden = isHidden, instanceId = instanceId, ) } } } is PromotedNotificationContentModel.When.Chronometer -> { is PromotedNotificationContentModel.When.Chronometer -> { return OngoingActivityChipModel.Active.Timer( OngoingActivityChipModel.Active.Timer( key = this.key, key = this.key, icon = icon, icon = icon, colors = colors, colors = colors, startTimeMs = chipContent.time.elapsedRealtimeMillis, startTimeMs = time.elapsedRealtimeMillis, isEventInFuture = chipContent.time.isCountDown, isEventInFuture = time.isCountDown, onClickListenerLegacy = onClickListenerLegacy, onClickListenerLegacy = onClickListenerLegacy, clickBehavior = clickBehavior, clickBehavior = clickBehavior, isHidden = isHidden, isHidden = isHidden, Loading @@ -236,6 +263,31 @@ constructor( ) ) } } /** * Model that prunes data from [NotificationChipModel] to just the information the status bar * chip needs. * * Used so that we don't re-create the chip [OngoingActivityChipModel] classes with new click * listeners unless absolutely necessary, which helps the chips re-compose less frequently. See * b/393456147. */ private data class PrunedNotificationChipModel( val key: String, val appName: String, val statusBarChipIconView: StatusBarIconView?, /** * The text to show in the chip, or null if text shouldn't be shown. Text takes precedence * over [time]. */ val text: String?, /** The time to show in the chip, or null if the time shouldn't be shown. */ val time: PromotedNotificationContentModel.When?, /** See [PromotedNotificationContentModel.wasPromotedAutomatically]. */ val wasPromotedAutomatically: Boolean, val isAppVisible: Boolean, val instanceId: InstanceId?, ) companion object { companion object { /** /** * Notifications must have a `when` time of at least 1 minute in the future in order for the * Notifications must have a `when` time of at least 1 minute in the future in order for the Loading