Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt +11 −5 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -44,6 +45,7 @@ import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMaxOfOrDefault import androidx.compose.ui.util.fastSumBy import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey Loading Loading @@ -148,12 +150,16 @@ private fun ContentScope.BundleHeaderContent( modifier = Modifier.element(BundleHeader.Elements.TitleText).weight(1f), ) if (collapsed && viewModel.previewIcons.isNotEmpty()) { if (collapsed) { val currentPreviewIcons by viewModel.previewIcons.collectAsStateWithLifecycle(initialValue = emptyList()) if (currentPreviewIcons.isNotEmpty()) { BundlePreviewIcons( previewDrawables = viewModel.previewIcons, previewDrawables = currentPreviewIcons, modifier = Modifier.padding(start = 8.dp), ) } } ExpansionControl( collapsed = collapsed, Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt +71 −8 Original line number Diff line number Diff line Loading @@ -21,21 +21,27 @@ import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.os.UserHandle import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.InternalNotificationsApi import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.render.BundleBarn import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.row.data.model.AppData import com.android.systemui.statusbar.notification.row.data.repository.TEST_BUNDLE_SPEC import com.google.common.truth.Truth.assertThat import kotlin.test.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import kotlin.test.assertEquals @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -49,6 +55,12 @@ class BundleCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: BundleCoordinator private val pkg1 = "pkg1" private val pkg2 = "pkg2" private val user1 = UserHandle.of(0) private val user2 = UserHandle.of(1) @Before fun setUp() { MockitoAnnotations.initMocks(this) Loading @@ -58,7 +70,7 @@ class BundleCoordinatorTest : SysuiTestCase() { socialController, recsController, promoController, bundleBarn bundleBarn, ) } Loading Loading @@ -87,8 +99,8 @@ class BundleCoordinatorTest : SysuiTestCase() { fun promoSectioner() { assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType(PROMOTIONS_ID))) .isTrue() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))). isFalse() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))) .isFalse() } @Test Loading @@ -103,16 +115,67 @@ class BundleCoordinatorTest : SysuiTestCase() { assertEquals(coordinator.bundler.getBundleIdOrNull(unclassifiedEntry), null) } @Test fun testUpdateAppData_emptyChildren_setsEmptyAppList() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) assertThat(bundle.children).isEmpty() coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList).isEmpty() } @OptIn(InternalNotificationsApi::class) @Test fun testUpdateAppData_twoNotifs() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) val notif1 = NotificationEntryBuilder().setPkg(pkg1).setUser(user1).build() val notif2 = NotificationEntryBuilder().setPkg(pkg2).setUser(user2).build() bundle.addChild(notif1) bundle.addChild(notif2) coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList) .containsExactly(AppData(pkg1, user1), AppData(pkg2, user2)) } @OptIn(InternalNotificationsApi::class) @Test fun testUpdateAppData_notifAndGroup() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) val notif1 = NotificationEntryBuilder().setPkg(pkg1).setUser(user1).build() val group1 = GroupEntry("key", 0L) val groupChild = NotificationEntryBuilder().setPkg(pkg2).setUser(user2).build() group1.rawChildren.add(groupChild) val groupSummary = NotificationEntryBuilder() .setPkg(pkg2) .setUser(user2) .setGroupSummary(context, true) .build() group1.summary = groupSummary bundle.addChild(notif1) bundle.addChild(group1) coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList) .containsExactly(AppData(pkg1, user1), AppData(pkg2, user2)) } private fun makeEntryOfChannelType( type: String, buildBlock: NotificationEntryBuilder.() -> Unit = {} buildBlock: NotificationEntryBuilder.() -> Unit = {}, ): NotificationEntry { val channel: NotificationChannel = NotificationChannel(type, type, 2) val entry = NotificationEntryBuilder() .updateRanking { it.setChannel(channel) } .updateRanking { it.setChannel(channel) } .also(buildBlock) .build() return entry Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +11 −3 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.NonNull; import android.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; Loading @@ -36,7 +38,8 @@ public class GroupEntry extends ListEntry { Collections.unmodifiableList(mChildren); private int mUntruncatedChildCount; GroupEntry(String key, long creationTime) { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public GroupEntry(String key, long creationTime) { super(key, creationTime); } Loading @@ -55,7 +58,8 @@ public class GroupEntry extends ListEntry { return mUnmodifiableChildren; } void setSummary(@Nullable NotificationEntry summary) { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public void setSummary(@Nullable NotificationEntry summary) { mSummary = summary; } Loading @@ -71,7 +75,11 @@ public class GroupEntry extends ListEntry { mChildren.sort(c); } List<NotificationEntry> getRawChildren() { /** * @return modifiable list of NotificationEntry children */ @VisibleForTesting public List<NotificationEntry> getRawChildren() { return mChildren; } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +54 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.os.Build import android.os.SystemProperties import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.BundleSpec import com.android.systemui.statusbar.notification.collection.GroupEntry Loading @@ -40,6 +41,7 @@ import com.android.systemui.statusbar.notification.dagger.NewsHeader import com.android.systemui.statusbar.notification.dagger.PromoHeader import com.android.systemui.statusbar.notification.dagger.RecsHeader import com.android.systemui.statusbar.notification.dagger.SocialHeader import com.android.systemui.statusbar.notification.row.data.model.AppData import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO Loading @@ -65,7 +67,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == NEWS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return newsHeaderController } } Loading @@ -76,7 +78,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return socialHeaderController } } Loading @@ -87,7 +89,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == RECS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return recsHeaderController } } Loading @@ -98,14 +100,13 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == PROMOTIONS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return promoHeaderController } } val bundler = object : NotifBundler("NotifBundler") { // Use list instead of set to keep fixed order override val bundleSpecs: List<BundleSpec> = buildList { add(BundleSpec.NEWS) Loading Loading @@ -145,20 +146,22 @@ constructor( } /** Recursively check parents until finding bundle or null */ private fun PipelineEntry.getBundleOrNull(): BundleEntry? { return when (this) { private fun PipelineEntry.getBundleOrNull(): BundleEntry? = when (this) { is BundleEntry -> this is ListEntry -> parent?.getBundleOrNull() } } private fun inflateAllBundleEntries(list: List<PipelineEntry>) { list.filterIsInstance<BundleEntry>().forEach { bundleBarn.inflateBundleEntry(it) } private fun inflateAllBundleEntries(entries: List<PipelineEntry>) { for (entry in entries) { if (entry is BundleEntry) { bundleBarn.inflateBundleEntry(entry) } } } private val bundleFilter: NotifFilter = object : NotifFilter("BundleInflateFilter") { override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean { // TODO(b/399736937) Do not hide notifications if we have a bug that means the // bundle isn't inflated yet. It's better that we just show those notifications in Loading @@ -175,7 +178,45 @@ constructor( } private val bundleCountUpdater = OnBeforeRenderListListener { entries -> entries.filterIsInstance<BundleEntry>().forEach(BundleEntry::updateTotalCount) for (entry in entries) { if (entry is BundleEntry) { entry.updateTotalCount() } } } /** * For each BundleEntry, populate its bundleRepository.appDataList with AppData (package name, * UserHandle) from its notif children */ @get:VisibleForTesting val bundleAppDataUpdater = OnBeforeRenderListListener { entries -> for (entry in entries) { if (entry !is BundleEntry) continue val newAppDataList: List<AppData> = entry.children.flatMap { listEntry -> when (listEntry) { is NotificationEntry -> { listOf(AppData(listEntry.sbn.packageName, listEntry.sbn.user)) } is GroupEntry -> { val summary = listEntry.representativeEntry if (summary != null) { listOf(AppData(summary.sbn.packageName, summary.sbn.user)) } else { error( "BundleEntry (key: ${entry.key}) contains GroupEntry " + "(key: ${listEntry.key}) with no summary." ) } } else -> error("Unexpected ListEntry type: ${listEntry::class.simpleName}") } } entry.bundleRepository.appDataList = newAppDataList } } override fun attach(pipeline: NotifPipeline) { Loading @@ -184,6 +225,7 @@ constructor( pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addFinalizeFilter(bundleFilter) pipeline.addOnBeforeRenderListListener(bundleCountUpdater) pipeline.addOnBeforeRenderListListener(bundleAppDataUpdater) } } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/BundleBarn.kt +31 −20 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.ViewGroup import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.PipelineDumpable Loading @@ -30,16 +32,16 @@ import com.android.systemui.statusbar.notification.row.RowInflaterTask import com.android.systemui.statusbar.notification.row.RowInflaterTaskLogger import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent import com.android.systemui.statusbar.notification.row.domain.interactor.BundleInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModel import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.util.time.SystemClock import dagger.Lazy import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineDispatcher /** * Class that handles inflating BundleEntry view and controller, for use by NodeSpecBuilder. */ /** Class that handles inflating BundleEntry view and controller, for use by NodeSpecBuilder. */ @SysUISingleton class BundleBarn @Inject Loading @@ -47,18 +49,18 @@ constructor( private val rowComponent: ExpandableNotificationRowComponent.Builder, private val rowInflaterTaskProvider: Provider<RowInflaterTask>, private val listContainer: NotificationListContainer, val context: Context? = null, @ShadeDisplayAware val context: Context, val systemClock: SystemClock, val logger: RowInflaterTaskLogger, val userTracker: UserTracker, private val presenterLazy: Lazy<NotificationPresenter?>? = null, private val appIconProvider: AppIconProvider, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : PipelineDumpable { /** * Map of [BundleEntry] key to [NodeController]: * no key -> not started * key maps to null -> inflating * key maps to controller -> inflated * Map of [BundleEntry] key to [NodeController]: no key -> not started key maps to null -> * inflating key maps to controller -> inflated */ private val keyToControllerMap = mutableMapOf<String, NotifViewController?>() Loading Loading @@ -86,9 +88,14 @@ constructor( keyToControllerMap[bundleEntry.key] = controller // TODO(389839492): Construct BundleHeaderViewModel (or even ENRViewModel) by dagger row.initBundleHeader( BundleHeaderViewModel(BundleInteractor(bundleEntry.bundleRepository)) val bundleInteractor = BundleInteractor( repository = bundleEntry.bundleRepository, appIconProvider = appIconProvider, context = context, backgroundDispatcher = backgroundDispatcher, ) row.initBundleHeader(BundleHeaderViewModel(bundleInteractor)) } debugBundleLog(TAG, { "calling inflate: ${bundleEntry.key}" }) keyToControllerMap[bundleEntry.key] = null Loading @@ -104,10 +111,13 @@ constructor( /** Return ExpandableNotificationRowController for BundleEntry. */ fun requireNodeController(bundleEntry: BundleEntry): NodeController { debugBundleLog(TAG, { debugBundleLog( TAG, { "requireNodeController: ${bundleEntry.key}" + "controller: ${keyToControllerMap[bundleEntry.key]}" }) }, ) return keyToControllerMap[bundleEntry.key] ?: error("No view has been registered for bundle: ${bundleEntry.key}") } Loading @@ -119,7 +129,8 @@ constructor( } else { d.println("Bundle Inflation States:") keyToControllerMap.forEach { (key, controller) -> val stateString = if (controller == null) { val stateString = if (controller == null) { "INFLATING" } else { "INFLATED (Controller: ${controller::class.simpleName})" Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/row/BundleHeader.kt +11 −5 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier Loading @@ -44,6 +45,7 @@ import androidx.compose.ui.util.fastMap import androidx.compose.ui.util.fastMaxOfOrDefault import androidx.compose.ui.util.fastSumBy import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.ElementKey Loading Loading @@ -148,12 +150,16 @@ private fun ContentScope.BundleHeaderContent( modifier = Modifier.element(BundleHeader.Elements.TitleText).weight(1f), ) if (collapsed && viewModel.previewIcons.isNotEmpty()) { if (collapsed) { val currentPreviewIcons by viewModel.previewIcons.collectAsStateWithLifecycle(initialValue = emptyList()) if (currentPreviewIcons.isNotEmpty()) { BundlePreviewIcons( previewDrawables = viewModel.previewIcons, previewDrawables = currentPreviewIcons, modifier = Modifier.padding(start = 8.dp), ) } } ExpansionControl( collapsed = collapsed, Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt +71 −8 Original line number Diff line number Diff line Loading @@ -21,21 +21,27 @@ import android.app.NotificationChannel.NEWS_ID import android.app.NotificationChannel.PROMOTIONS_ID import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.os.UserHandle import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.InternalNotificationsApi import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.render.BundleBarn import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.row.data.model.AppData import com.android.systemui.statusbar.notification.row.data.repository.TEST_BUNDLE_SPEC import com.google.common.truth.Truth.assertThat import kotlin.test.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import kotlin.test.assertEquals @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -49,6 +55,12 @@ class BundleCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: BundleCoordinator private val pkg1 = "pkg1" private val pkg2 = "pkg2" private val user1 = UserHandle.of(0) private val user2 = UserHandle.of(1) @Before fun setUp() { MockitoAnnotations.initMocks(this) Loading @@ -58,7 +70,7 @@ class BundleCoordinatorTest : SysuiTestCase() { socialController, recsController, promoController, bundleBarn bundleBarn, ) } Loading Loading @@ -87,8 +99,8 @@ class BundleCoordinatorTest : SysuiTestCase() { fun promoSectioner() { assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType(PROMOTIONS_ID))) .isTrue() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))). isFalse() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))) .isFalse() } @Test Loading @@ -103,16 +115,67 @@ class BundleCoordinatorTest : SysuiTestCase() { assertEquals(coordinator.bundler.getBundleIdOrNull(unclassifiedEntry), null) } @Test fun testUpdateAppData_emptyChildren_setsEmptyAppList() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) assertThat(bundle.children).isEmpty() coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList).isEmpty() } @OptIn(InternalNotificationsApi::class) @Test fun testUpdateAppData_twoNotifs() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) val notif1 = NotificationEntryBuilder().setPkg(pkg1).setUser(user1).build() val notif2 = NotificationEntryBuilder().setPkg(pkg2).setUser(user2).build() bundle.addChild(notif1) bundle.addChild(notif2) coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList) .containsExactly(AppData(pkg1, user1), AppData(pkg2, user2)) } @OptIn(InternalNotificationsApi::class) @Test fun testUpdateAppData_notifAndGroup() { val bundle = BundleEntry(TEST_BUNDLE_SPEC) val notif1 = NotificationEntryBuilder().setPkg(pkg1).setUser(user1).build() val group1 = GroupEntry("key", 0L) val groupChild = NotificationEntryBuilder().setPkg(pkg2).setUser(user2).build() group1.rawChildren.add(groupChild) val groupSummary = NotificationEntryBuilder() .setPkg(pkg2) .setUser(user2) .setGroupSummary(context, true) .build() group1.summary = groupSummary bundle.addChild(notif1) bundle.addChild(group1) coordinator.bundleAppDataUpdater.onBeforeRenderList(listOf(bundle)) assertThat(bundle.bundleRepository.appDataList) .containsExactly(AppData(pkg1, user1), AppData(pkg2, user2)) } private fun makeEntryOfChannelType( type: String, buildBlock: NotificationEntryBuilder.() -> Unit = {} buildBlock: NotificationEntryBuilder.() -> Unit = {}, ): NotificationEntry { val channel: NotificationChannel = NotificationChannel(type, type, 2) val entry = NotificationEntryBuilder() .updateRanking { it.setChannel(channel) } .updateRanking { it.setChannel(channel) } .also(buildBlock) .build() return entry Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +11 −3 Original line number Diff line number Diff line Loading @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.NonNull; import android.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; Loading @@ -36,7 +38,8 @@ public class GroupEntry extends ListEntry { Collections.unmodifiableList(mChildren); private int mUntruncatedChildCount; GroupEntry(String key, long creationTime) { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public GroupEntry(String key, long creationTime) { super(key, creationTime); } Loading @@ -55,7 +58,8 @@ public class GroupEntry extends ListEntry { return mUnmodifiableChildren; } void setSummary(@Nullable NotificationEntry summary) { @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public void setSummary(@Nullable NotificationEntry summary) { mSummary = summary; } Loading @@ -71,7 +75,11 @@ public class GroupEntry extends ListEntry { mChildren.sort(c); } List<NotificationEntry> getRawChildren() { /** * @return modifiable list of NotificationEntry children */ @VisibleForTesting public List<NotificationEntry> getRawChildren() { return mChildren; } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt +54 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.app.NotificationChannel.RECS_ID import android.app.NotificationChannel.SOCIAL_MEDIA_ID import android.os.Build import android.os.SystemProperties import androidx.annotation.VisibleForTesting import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.BundleSpec import com.android.systemui.statusbar.notification.collection.GroupEntry Loading @@ -40,6 +41,7 @@ import com.android.systemui.statusbar.notification.dagger.NewsHeader import com.android.systemui.statusbar.notification.dagger.PromoHeader import com.android.systemui.statusbar.notification.dagger.RecsHeader import com.android.systemui.statusbar.notification.dagger.SocialHeader import com.android.systemui.statusbar.notification.row.data.model.AppData import com.android.systemui.statusbar.notification.shared.NotificationBundleUi import com.android.systemui.statusbar.notification.stack.BUCKET_NEWS import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO Loading @@ -65,7 +67,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == NEWS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return newsHeaderController } } Loading @@ -76,7 +78,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return socialHeaderController } } Loading @@ -87,7 +89,7 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == RECS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return recsHeaderController } } Loading @@ -98,14 +100,13 @@ constructor( return entry.asListEntry()?.representativeEntry?.channel?.id == PROMOTIONS_ID } override fun getHeaderNodeController(): NodeController? { override fun getHeaderNodeController(): NodeController { return promoHeaderController } } val bundler = object : NotifBundler("NotifBundler") { // Use list instead of set to keep fixed order override val bundleSpecs: List<BundleSpec> = buildList { add(BundleSpec.NEWS) Loading Loading @@ -145,20 +146,22 @@ constructor( } /** Recursively check parents until finding bundle or null */ private fun PipelineEntry.getBundleOrNull(): BundleEntry? { return when (this) { private fun PipelineEntry.getBundleOrNull(): BundleEntry? = when (this) { is BundleEntry -> this is ListEntry -> parent?.getBundleOrNull() } } private fun inflateAllBundleEntries(list: List<PipelineEntry>) { list.filterIsInstance<BundleEntry>().forEach { bundleBarn.inflateBundleEntry(it) } private fun inflateAllBundleEntries(entries: List<PipelineEntry>) { for (entry in entries) { if (entry is BundleEntry) { bundleBarn.inflateBundleEntry(entry) } } } private val bundleFilter: NotifFilter = object : NotifFilter("BundleInflateFilter") { override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean { // TODO(b/399736937) Do not hide notifications if we have a bug that means the // bundle isn't inflated yet. It's better that we just show those notifications in Loading @@ -175,7 +178,45 @@ constructor( } private val bundleCountUpdater = OnBeforeRenderListListener { entries -> entries.filterIsInstance<BundleEntry>().forEach(BundleEntry::updateTotalCount) for (entry in entries) { if (entry is BundleEntry) { entry.updateTotalCount() } } } /** * For each BundleEntry, populate its bundleRepository.appDataList with AppData (package name, * UserHandle) from its notif children */ @get:VisibleForTesting val bundleAppDataUpdater = OnBeforeRenderListListener { entries -> for (entry in entries) { if (entry !is BundleEntry) continue val newAppDataList: List<AppData> = entry.children.flatMap { listEntry -> when (listEntry) { is NotificationEntry -> { listOf(AppData(listEntry.sbn.packageName, listEntry.sbn.user)) } is GroupEntry -> { val summary = listEntry.representativeEntry if (summary != null) { listOf(AppData(summary.sbn.packageName, summary.sbn.user)) } else { error( "BundleEntry (key: ${entry.key}) contains GroupEntry " + "(key: ${listEntry.key}) with no summary." ) } } else -> error("Unexpected ListEntry type: ${listEntry::class.simpleName}") } } entry.bundleRepository.appDataList = newAppDataList } } override fun attach(pipeline: NotifPipeline) { Loading @@ -184,6 +225,7 @@ constructor( pipeline.addOnBeforeFinalizeFilterListener(this::inflateAllBundleEntries) pipeline.addFinalizeFilter(bundleFilter) pipeline.addOnBeforeRenderListListener(bundleCountUpdater) pipeline.addOnBeforeRenderListListener(bundleAppDataUpdater) } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/BundleBarn.kt +31 −20 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context import android.view.ViewGroup import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.collection.BundleEntry import com.android.systemui.statusbar.notification.collection.PipelineDumpable Loading @@ -30,16 +32,16 @@ import com.android.systemui.statusbar.notification.row.RowInflaterTask import com.android.systemui.statusbar.notification.row.RowInflaterTaskLogger import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent import com.android.systemui.statusbar.notification.row.domain.interactor.BundleInteractor import com.android.systemui.statusbar.notification.row.icon.AppIconProvider import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModel import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.util.time.SystemClock import dagger.Lazy import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineDispatcher /** * Class that handles inflating BundleEntry view and controller, for use by NodeSpecBuilder. */ /** Class that handles inflating BundleEntry view and controller, for use by NodeSpecBuilder. */ @SysUISingleton class BundleBarn @Inject Loading @@ -47,18 +49,18 @@ constructor( private val rowComponent: ExpandableNotificationRowComponent.Builder, private val rowInflaterTaskProvider: Provider<RowInflaterTask>, private val listContainer: NotificationListContainer, val context: Context? = null, @ShadeDisplayAware val context: Context, val systemClock: SystemClock, val logger: RowInflaterTaskLogger, val userTracker: UserTracker, private val presenterLazy: Lazy<NotificationPresenter?>? = null, private val appIconProvider: AppIconProvider, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : PipelineDumpable { /** * Map of [BundleEntry] key to [NodeController]: * no key -> not started * key maps to null -> inflating * key maps to controller -> inflated * Map of [BundleEntry] key to [NodeController]: no key -> not started key maps to null -> * inflating key maps to controller -> inflated */ private val keyToControllerMap = mutableMapOf<String, NotifViewController?>() Loading Loading @@ -86,9 +88,14 @@ constructor( keyToControllerMap[bundleEntry.key] = controller // TODO(389839492): Construct BundleHeaderViewModel (or even ENRViewModel) by dagger row.initBundleHeader( BundleHeaderViewModel(BundleInteractor(bundleEntry.bundleRepository)) val bundleInteractor = BundleInteractor( repository = bundleEntry.bundleRepository, appIconProvider = appIconProvider, context = context, backgroundDispatcher = backgroundDispatcher, ) row.initBundleHeader(BundleHeaderViewModel(bundleInteractor)) } debugBundleLog(TAG, { "calling inflate: ${bundleEntry.key}" }) keyToControllerMap[bundleEntry.key] = null Loading @@ -104,10 +111,13 @@ constructor( /** Return ExpandableNotificationRowController for BundleEntry. */ fun requireNodeController(bundleEntry: BundleEntry): NodeController { debugBundleLog(TAG, { debugBundleLog( TAG, { "requireNodeController: ${bundleEntry.key}" + "controller: ${keyToControllerMap[bundleEntry.key]}" }) }, ) return keyToControllerMap[bundleEntry.key] ?: error("No view has been registered for bundle: ${bundleEntry.key}") } Loading @@ -119,7 +129,8 @@ constructor( } else { d.println("Bundle Inflation States:") keyToControllerMap.forEach { (key, controller) -> val stateString = if (controller == null) { val stateString = if (controller == null) { "INFLATING" } else { "INFLATED (Controller: ${controller::class.simpleName})" Loading