Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.coordinator import android.app.NotificationChannel 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.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.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.render.NodeController import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class BundleCoordinatorTest : SysuiTestCase() { @Mock private lateinit var newsController: NodeController @Mock private lateinit var socialController: NodeController @Mock private lateinit var recsController: NodeController @Mock private lateinit var promoController: NodeController private lateinit var coordinator: BundleCoordinator @Before fun setUp() { MockitoAnnotations.initMocks(this) coordinator = BundleCoordinator( newsController, socialController, recsController, promoController ) } @Test fun newsSectioner() { assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType(NEWS_ID))).isTrue() assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType("news"))).isFalse() } @Test fun socialSectioner() { assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType(SOCIAL_MEDIA_ID))) .isTrue() assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType("social"))) .isFalse() } @Test fun recsSectioner() { assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType(RECS_ID))).isTrue() assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType("recommendations"))) .isFalse() } @Test fun promoSectioner() { assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType(PROMOTIONS_ID))) .isTrue() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))). isFalse() } private fun makeEntryOfChannelType( type: String, buildBlock: NotificationEntryBuilder.() -> Unit = {} ): NotificationEntry { val channel: NotificationChannel = NotificationChannel(type, type, 2) val entry = NotificationEntryBuilder() .updateRanking { it.setChannel(channel) } .also(buildBlock) .build() return entry } } packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +3 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING Loading Loading @@ -51,7 +52,8 @@ constructor(val proxy: DeviceConfigProxy, val context: Context) { } fun getNotificationBuckets(): IntArray { if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled) { if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled || NotificationClassificationFlag.isEnabled) { // We don't need this list to be adaptive, it can be the superset of all features. return PriorityBucket.getAllInOrder() } Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationClassificationFlag.kt 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.service.notification.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils /** * Helper for android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION */ @Suppress("NOTHING_TO_INLINE") object NotificationClassificationFlag { const val FLAG_NAME = Flags.FLAG_NOTIFICATION_CLASSIFICATION /** A token used for dependency declaration */ val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled) /** Are sections sorted by time? */ @JvmStatic inline val isEnabled get() = Flags.notificationClassification() /** * Called to ensure code is only run when the flag is enabled. This protects users from the * unintended behaviors caused by accidentally running new logic, while also crashing on an eng * build to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun isUnexpectedlyInLegacyMode() = RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) } No newline at end of file packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.coordinator 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 com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController 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.stack.BUCKET_NEWS import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO import com.android.systemui.statusbar.notification.stack.BUCKET_RECS import com.android.systemui.statusbar.notification.stack.BUCKET_SOCIAL import javax.inject.Inject /** * Coordinator for sections derived from NotificationAssistantService classification. */ @CoordinatorScope class BundleCoordinator @Inject constructor( @NewsHeader private val newsHeaderController: NodeController, @SocialHeader private val socialHeaderController: NodeController, @RecsHeader private val recsHeaderController: NodeController, @PromoHeader private val promoHeaderController: NodeController, ) : Coordinator { val newsSectioner = object : NotifSectioner("News", BUCKET_NEWS) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == NEWS_ID } override fun getHeaderNodeController(): NodeController? { return newsHeaderController } } val socialSectioner = object : NotifSectioner("Social", BUCKET_SOCIAL) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID } override fun getHeaderNodeController(): NodeController? { return socialHeaderController } } val recsSectioner = object : NotifSectioner("Recommendations", BUCKET_RECS) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == RECS_ID } override fun getHeaderNodeController(): NodeController? { return recsHeaderController } } val promoSectioner = object : NotifSectioner("Promotions", BUCKET_PROMO) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == PROMOTIONS_ID } override fun getHeaderNodeController(): NodeController? { return promoHeaderController } } override fun attach(pipeline: NotifPipeline) { } } packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +8 −4 Original line number Diff line number Diff line Loading @@ -17,10 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.collection.* import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider Loading Loading @@ -69,6 +66,7 @@ constructor( dismissibilityCoordinator: DismissibilityCoordinator, dreamCoordinator: DreamCoordinator, statsLoggerCoordinator: NotificationStatsLoggerCoordinator, bundleCoordinator: BundleCoordinator, ) : NotifCoordinators { private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList() Loading Loading @@ -132,6 +130,12 @@ constructor( mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent } mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting if (NotificationClassificationFlag.isEnabled) { mOrderedSections.add(bundleCoordinator.newsSectioner); mOrderedSections.add(bundleCoordinator.socialSectioner); mOrderedSections.add(bundleCoordinator.recsSectioner); mOrderedSections.add(bundleCoordinator.promoSectioner); } mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinatorTest.kt 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.coordinator import android.app.NotificationChannel 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.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.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.render.NodeController import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class BundleCoordinatorTest : SysuiTestCase() { @Mock private lateinit var newsController: NodeController @Mock private lateinit var socialController: NodeController @Mock private lateinit var recsController: NodeController @Mock private lateinit var promoController: NodeController private lateinit var coordinator: BundleCoordinator @Before fun setUp() { MockitoAnnotations.initMocks(this) coordinator = BundleCoordinator( newsController, socialController, recsController, promoController ) } @Test fun newsSectioner() { assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType(NEWS_ID))).isTrue() assertThat(coordinator.newsSectioner.isInSection(makeEntryOfChannelType("news"))).isFalse() } @Test fun socialSectioner() { assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType(SOCIAL_MEDIA_ID))) .isTrue() assertThat(coordinator.socialSectioner.isInSection(makeEntryOfChannelType("social"))) .isFalse() } @Test fun recsSectioner() { assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType(RECS_ID))).isTrue() assertThat(coordinator.recsSectioner.isInSection(makeEntryOfChannelType("recommendations"))) .isFalse() } @Test fun promoSectioner() { assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType(PROMOTIONS_ID))) .isTrue() assertThat(coordinator.promoSectioner.isInSection(makeEntryOfChannelType("promo"))). isFalse() } private fun makeEntryOfChannelType( type: String, buildBlock: NotificationEntryBuilder.() -> Unit = {} ): NotificationEntry { val channel: NotificationChannel = NotificationChannel(type, type, 2) val entry = NotificationEntryBuilder() .updateRanking { it.setChannel(channel) } .also(buildBlock) .build() return entry } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +3 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING Loading Loading @@ -51,7 +52,8 @@ constructor(val proxy: DeviceConfigProxy, val context: Context) { } fun getNotificationBuckets(): IntArray { if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled) { if (PriorityPeopleSection.isEnabled || NotificationMinimalismPrototype.V2.isEnabled || NotificationClassificationFlag.isEnabled) { // We don't need this list to be adaptive, it can be the superset of all features. return PriorityBucket.getAllInOrder() } Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationClassificationFlag.kt 0 → 100644 +53 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.service.notification.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils /** * Helper for android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION */ @Suppress("NOTHING_TO_INLINE") object NotificationClassificationFlag { const val FLAG_NAME = Flags.FLAG_NOTIFICATION_CLASSIFICATION /** A token used for dependency declaration */ val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled) /** Are sections sorted by time? */ @JvmStatic inline val isEnabled get() = Flags.notificationClassification() /** * Called to ensure code is only run when the flag is enabled. This protects users from the * unintended behaviors caused by accidentally running new logic, while also crashing on an eng * build to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun isUnexpectedlyInLegacyMode() = RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) /** * Called to ensure code is only run when the flag is disabled. This will throw an exception if * the flag is enabled to ensure that the refactor author catches issues in testing. */ @JvmStatic inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) } No newline at end of file
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BundleCoordinator.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.coordinator 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 com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController 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.stack.BUCKET_NEWS import com.android.systemui.statusbar.notification.stack.BUCKET_PROMO import com.android.systemui.statusbar.notification.stack.BUCKET_RECS import com.android.systemui.statusbar.notification.stack.BUCKET_SOCIAL import javax.inject.Inject /** * Coordinator for sections derived from NotificationAssistantService classification. */ @CoordinatorScope class BundleCoordinator @Inject constructor( @NewsHeader private val newsHeaderController: NodeController, @SocialHeader private val socialHeaderController: NodeController, @RecsHeader private val recsHeaderController: NodeController, @PromoHeader private val promoHeaderController: NodeController, ) : Coordinator { val newsSectioner = object : NotifSectioner("News", BUCKET_NEWS) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == NEWS_ID } override fun getHeaderNodeController(): NodeController? { return newsHeaderController } } val socialSectioner = object : NotifSectioner("Social", BUCKET_SOCIAL) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == SOCIAL_MEDIA_ID } override fun getHeaderNodeController(): NodeController? { return socialHeaderController } } val recsSectioner = object : NotifSectioner("Recommendations", BUCKET_RECS) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == RECS_ID } override fun getHeaderNodeController(): NodeController? { return recsHeaderController } } val promoSectioner = object : NotifSectioner("Promotions", BUCKET_PROMO) { override fun isInSection(entry: ListEntry): Boolean { return entry.representativeEntry?.channel?.id == PROMOTIONS_ID } override fun getHeaderNodeController(): NodeController? { return promoHeaderController } } override fun attach(pipeline: NotifPipeline) { } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +8 −4 Original line number Diff line number Diff line Loading @@ -17,10 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.collection.* import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider Loading Loading @@ -69,6 +66,7 @@ constructor( dismissibilityCoordinator: DismissibilityCoordinator, dreamCoordinator: DreamCoordinator, statsLoggerCoordinator: NotificationStatsLoggerCoordinator, bundleCoordinator: BundleCoordinator, ) : NotifCoordinators { private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList() Loading Loading @@ -132,6 +130,12 @@ constructor( mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent } mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting if (NotificationClassificationFlag.isEnabled) { mOrderedSections.add(bundleCoordinator.newsSectioner); mOrderedSections.add(bundleCoordinator.socialSectioner); mOrderedSections.add(bundleCoordinator.recsSectioner); mOrderedSections.add(bundleCoordinator.promoSectioner); } mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized Loading