Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt 0 → 100644 +137 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.collection import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.data.repository.TEST_BUNDLE_SPEC import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper @EnableFlags(NotificationBundleUi.FLAG_NAME) class BundleEntryTest : SysuiTestCase() { private lateinit var bundleEntry: BundleEntry @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setUp() { bundleEntry = BundleEntry(TEST_BUNDLE_SPEC) } @Test fun testTotalCount_initial_isZero() { assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(0) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addNotif() { bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addGroup() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addMultipleGroups() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) val groupEntry2 = GroupEntry("key", 0) groupEntry2.addChild(NotificationEntryBuilder().build()) groupEntry2.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry2) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(3) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addNotifAndGroup() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_removeNotif() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) val bundleNotifChild = NotificationEntryBuilder().build() bundleEntry.addChild(bundleNotifChild) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) bundleEntry.removeChild(bundleNotifChild) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_removeGroupChild() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) groupEntry1.clearChildren() // Explicitly call updateTotalCount, which is what ShadeListBuilder does via // BundleCoordinator's OnBeforeRenderListListener before rendering. bundleEntry.updateTotalCount() assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_clearChildren() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) bundleEntry.clearChildren() assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(0) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt +24 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ */ package com.android.systemui.statusbar.notification.collection package com.android.systemui.statusbar.notification.collection import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.data.repository.BundleRepository import com.android.systemui.statusbar.notification.row.data.repository.BundleRepository import java.util.Collections import java.util.Collections Loading Loading @@ -47,16 +48,19 @@ class BundleEntry(spec: BundleSpec) : PipelineEntry(spec.key) { @InternalNotificationsApi @InternalNotificationsApi fun addChild(child: ListEntry) { fun addChild(child: ListEntry) { _children.add(child) _children.add(child) updateTotalCount() } } @InternalNotificationsApi @InternalNotificationsApi fun removeChild(child: ListEntry) { fun removeChild(child: ListEntry) { _children.remove(child) _children.remove(child) updateTotalCount() } } @InternalNotificationsApi @InternalNotificationsApi fun clearChildren() { fun clearChildren() { _children.clear() _children.clear() updateTotalCount() } } override fun asListEntry(): ListEntry? { override fun asListEntry(): ListEntry? { Loading @@ -74,4 +78,24 @@ class BundleEntry(spec: BundleSpec) : PipelineEntry(spec.key) { */ */ val isClearable: Boolean val isClearable: Boolean get() = _children.all { it.representativeEntry?.sbn?.isClearable != false } get() = _children.all { it.representativeEntry?.sbn?.isClearable != false } /** * The total count of [NotificationEntry]s within bundle. Notification updates trigger * pipeline rebuilds, so updates to group children will be reflected in this count. */ @VisibleForTesting fun updateTotalCount() { var count = 0 for (child in _children) { when (child) { is NotificationEntry -> { count++ } is GroupEntry -> { count += child.getChildren().size } } } bundleRepository.numberOfChildren = count } } } packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +7 −0 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner Loading Loading @@ -168,11 +169,17 @@ constructor( } } } } private val bundleCountUpdater = OnBeforeRenderListListener { entries -> entries.filterIsInstance<BundleEntry>() .forEach(BundleEntry::updateTotalCount) } override fun attach(pipeline: NotifPipeline) { override fun attach(pipeline: NotifPipeline) { if (NotificationBundleUi.isEnabled) { if (NotificationBundleUi.isEnabled) { pipeline.setNotifBundler(bundler) pipeline.setNotifBundler(bundler) pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addFinalizeFilter(bundleFilter) pipeline.addFinalizeFilter(bundleFilter) pipeline.addOnBeforeRenderListListener(bundleCountUpdater) } } } } Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryTest.kt 0 → 100644 +137 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.notification.collection import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.data.repository.TEST_BUNDLE_SPEC import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper @EnableFlags(NotificationBundleUi.FLAG_NAME) class BundleEntryTest : SysuiTestCase() { private lateinit var bundleEntry: BundleEntry @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setUp() { bundleEntry = BundleEntry(TEST_BUNDLE_SPEC) } @Test fun testTotalCount_initial_isZero() { assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(0) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addNotif() { bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addGroup() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addMultipleGroups() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) val groupEntry2 = GroupEntry("key", 0) groupEntry2.addChild(NotificationEntryBuilder().build()) groupEntry2.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry2) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(3) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_addNotifAndGroup() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_removeNotif() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) val bundleNotifChild = NotificationEntryBuilder().build() bundleEntry.addChild(bundleNotifChild) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) bundleEntry.removeChild(bundleNotifChild) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_removeGroupChild() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) groupEntry1.clearChildren() // Explicitly call updateTotalCount, which is what ShadeListBuilder does via // BundleCoordinator's OnBeforeRenderListListener before rendering. bundleEntry.updateTotalCount() assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(1) } @OptIn(InternalNotificationsApi::class) @Test fun testTotalCount_clearChildren() { val groupEntry1 = GroupEntry("key", 0) groupEntry1.addChild(NotificationEntryBuilder().build()) bundleEntry.addChild(groupEntry1) bundleEntry.addChild(NotificationEntryBuilder().build()) assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(2) bundleEntry.clearChildren() assertThat(bundleEntry.bundleRepository.numberOfChildren).isEqualTo(0) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt +24 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ */ package com.android.systemui.statusbar.notification.collection package com.android.systemui.statusbar.notification.collection import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.data.repository.BundleRepository import com.android.systemui.statusbar.notification.row.data.repository.BundleRepository import java.util.Collections import java.util.Collections Loading Loading @@ -47,16 +48,19 @@ class BundleEntry(spec: BundleSpec) : PipelineEntry(spec.key) { @InternalNotificationsApi @InternalNotificationsApi fun addChild(child: ListEntry) { fun addChild(child: ListEntry) { _children.add(child) _children.add(child) updateTotalCount() } } @InternalNotificationsApi @InternalNotificationsApi fun removeChild(child: ListEntry) { fun removeChild(child: ListEntry) { _children.remove(child) _children.remove(child) updateTotalCount() } } @InternalNotificationsApi @InternalNotificationsApi fun clearChildren() { fun clearChildren() { _children.clear() _children.clear() updateTotalCount() } } override fun asListEntry(): ListEntry? { override fun asListEntry(): ListEntry? { Loading @@ -74,4 +78,24 @@ class BundleEntry(spec: BundleSpec) : PipelineEntry(spec.key) { */ */ val isClearable: Boolean val isClearable: Boolean get() = _children.all { it.representativeEntry?.sbn?.isClearable != false } get() = _children.all { it.representativeEntry?.sbn?.isClearable != false } /** * The total count of [NotificationEntry]s within bundle. Notification updates trigger * pipeline rebuilds, so updates to group children will be reflected in this count. */ @VisibleForTesting fun updateTotalCount() { var count = 0 for (child in _children) { when (child) { is NotificationEntry -> { count++ } is GroupEntry -> { count += child.getChildren().size } } } bundleRepository.numberOfChildren = count } } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +7 −0 Original line number Original line Diff line number Diff line Loading @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.PipelineEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifBundler import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner Loading Loading @@ -168,11 +169,17 @@ constructor( } } } } private val bundleCountUpdater = OnBeforeRenderListListener { entries -> entries.filterIsInstance<BundleEntry>() .forEach(BundleEntry::updateTotalCount) } override fun attach(pipeline: NotifPipeline) { override fun attach(pipeline: NotifPipeline) { if (NotificationBundleUi.isEnabled) { if (NotificationBundleUi.isEnabled) { pipeline.setNotifBundler(bundler) pipeline.setNotifBundler(bundler) pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addFinalizeFilter(bundleFilter) pipeline.addFinalizeFilter(bundleFilter) pipeline.addOnBeforeRenderListListener(bundleCountUpdater) } } } } Loading