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

Commit e945e811 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Add shade sections for NAS classification

Test: BundleCoordinatorTest
Fixes: 350541684
Flag: android.service.notification.notification_classification
Change-Id: I0353411b3b6877cf034333dfbdca82491d1f7b39
parent 614b60cc
Loading
Loading
Loading
Loading
+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
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -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
@@ -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()
        }
+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
+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) {
    }
}
+8 −4
Original line number Diff line number Diff line
@@ -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
@@ -69,6 +66,7 @@ constructor(
    dismissibilityCoordinator: DismissibilityCoordinator,
    dreamCoordinator: DreamCoordinator,
    statsLoggerCoordinator: NotificationStatsLoggerCoordinator,
    bundleCoordinator: BundleCoordinator,
) : NotifCoordinators {

    private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -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