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

Commit fbe52695 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "[RONs] Sort notifications in Ongoing section by chip order." into main

parents 1e591487 fccc4d93
Loading
Loading
Loading
Loading
+53 −1
Original line number Diff line number Diff line
@@ -27,14 +27,29 @@ 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.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.buildNotificationEntry
import com.android.systemui.statusbar.notification.buildOngoingCallEntry
import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.collection.buildEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.notifPipeline
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.domain.interactor.promotedNotificationsInteractor
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -59,7 +74,13 @@ class ColorizedFgsCoordinatorTest : SysuiTestCase() {
    fun setup() {
        allowTestableLooperAsMainThread()

        colorizedFgsCoordinator = ColorizedFgsCoordinator()
        kosmos.statusBarNotificationChipsInteractor.start()

        colorizedFgsCoordinator =
            ColorizedFgsCoordinator(
                kosmos.applicationCoroutineScope,
                kosmos.promotedNotificationsInteractor,
            )
        colorizedFgsCoordinator.attach(notifPipeline)
        sectioner = colorizedFgsCoordinator.sectioner
    }
@@ -178,6 +199,37 @@ class ColorizedFgsCoordinatorTest : SysuiTestCase() {
        verify(notifPipeline, never()).addPromoter(any())
    }

    @Test
    @EnableFlags(
        PromotedNotificationUi.FLAG_NAME,
        StatusBarNotifChips.FLAG_NAME,
        StatusBarChipsModernization.FLAG_NAME,
        StatusBarRootModernization.FLAG_NAME,
    )
    fun comparatorPutsCallBeforeOther() =
        kosmos.runTest {
            // GIVEN a call and a promoted ongoing notification
            val callEntry = buildOngoingCallEntry(promoted = false)
            val ronEntry = buildPromotedOngoingEntry()
            val otherEntry = buildNotificationEntry(tag = "other")

            kosmos.renderNotificationListInteractor.setRenderedList(
                listOf(callEntry, ronEntry, otherEntry)
            )

            val orderedChipNotificationKeys by
                collectLastValue(kosmos.promotedNotificationsInteractor.orderedChipNotificationKeys)

            // THEN the order of the notification keys should be the call then the RON
            assertThat(orderedChipNotificationKeys)
                .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")

            // VERIFY that the comparator puts the call before the ron
            assertThat(sectioner.comparator!!.compare(callEntry, ronEntry)).isLessThan(0)
            // VERIFY that the comparator puts the ron before the other
            assertThat(sectioner.comparator!!.compare(ronEntry, otherEntry)).isLessThan(0)
        }

    private fun makeCallStyle(): Notification.CallStyle {
        val pendingIntent =
            PendingIntent.getBroadcast(mContext, 0, Intent("action"), PendingIntent.FLAG_IMMUTABLE)
+156 −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.promoted.domain.interactor

import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.buildNotificationEntry
import com.android.systemui.statusbar.notification.buildOngoingCallEntry
import com.android.systemui.statusbar.notification.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableFlags(
    PromotedNotificationUi.FLAG_NAME,
    StatusBarNotifChips.FLAG_NAME,
    StatusBarChipsModernization.FLAG_NAME,
    StatusBarRootModernization.FLAG_NAME,
)
class PromotedNotificationsInteractorTest : SysuiTestCase() {
    private val kosmos = testKosmos().useUnconfinedTestDispatcher()

    private val Kosmos.underTest by Fixture { promotedNotificationsInteractor }

    @Before
    fun setUp() {
        kosmos.statusBarNotificationChipsInteractor.start()
    }

    @Test
    fun orderedChipNotificationKeys_containsNonPromotedCalls() =
        kosmos.runTest {
            // GIVEN a call and a promoted ongoing notification
            val callEntry = buildOngoingCallEntry(promoted = false)
            val ronEntry = buildPromotedOngoingEntry()
            val otherEntry = buildNotificationEntry(tag = "other")

            renderNotificationListInteractor.setRenderedList(
                listOf(callEntry, ronEntry, otherEntry)
            )

            val orderedChipNotificationKeys by
                collectLastValue(underTest.orderedChipNotificationKeys)

            // THEN the order of the notification keys should be the call then the RON
            assertThat(orderedChipNotificationKeys)
                .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")
        }

    @Test
    fun orderedChipNotificationKeys_containsPromotedCalls() =
        kosmos.runTest {
            // GIVEN a call and a promoted ongoing notification
            val callEntry = buildOngoingCallEntry(promoted = true)
            val ronEntry = buildPromotedOngoingEntry()
            val otherEntry = buildNotificationEntry(tag = "other")

            renderNotificationListInteractor.setRenderedList(
                listOf(callEntry, ronEntry, otherEntry)
            )

            val orderedChipNotificationKeys by
                collectLastValue(underTest.orderedChipNotificationKeys)

            // THEN the order of the notification keys should be the call then the RON
            assertThat(orderedChipNotificationKeys)
                .containsExactly("0|test_pkg|0|call|0", "0|test_pkg|0|ron|0")
        }

    @Test
    fun topPromotedNotificationContent_skipsNonPromotedCalls() =
        kosmos.runTest {
            // GIVEN a non-promoted call and a promoted ongoing notification
            val callEntry = buildOngoingCallEntry(promoted = false)
            val ronEntry = buildPromotedOngoingEntry()
            val otherEntry = buildNotificationEntry(tag = "other")

            renderNotificationListInteractor.setRenderedList(
                listOf(callEntry, ronEntry, otherEntry)
            )

            val topPromotedNotificationContent by
                collectLastValue(underTest.topPromotedNotificationContent)

            // THEN the ron is first because the call has no content
            assertThat(topPromotedNotificationContent?.identity?.key)
                .isEqualTo("0|test_pkg|0|ron|0")
        }

    @Test
    fun topPromotedNotificationContent_includesPromotedCalls() =
        kosmos.runTest {
            // GIVEN a promoted call and a promoted ongoing notification
            val callEntry = buildOngoingCallEntry(promoted = true)
            val ronEntry = buildPromotedOngoingEntry()
            val otherEntry = buildNotificationEntry(tag = "other")

            renderNotificationListInteractor.setRenderedList(
                listOf(callEntry, ronEntry, otherEntry)
            )

            val topPromotedNotificationContent by
                collectLastValue(underTest.topPromotedNotificationContent)

            // THEN the call is the top notification
            assertThat(topPromotedNotificationContent?.identity?.key)
                .isEqualTo("0|test_pkg|0|call|0")
        }

    @Test
    fun topPromotedNotificationContent_nullWithNoPromotedNotifications() =
        kosmos.runTest {
            // GIVEN a a non-promoted call and no promoted ongoing entry
            val callEntry = buildOngoingCallEntry(promoted = false)
            val otherEntry = buildNotificationEntry(tag = "other")

            renderNotificationListInteractor.setRenderedList(listOf(callEntry, otherEntry))

            val topPromotedNotificationContent by
                collectLastValue(underTest.topPromotedNotificationContent)

            // THEN there is no top promoted notification
            assertThat(topPromotedNotificationContent).isNull()
        }
}
+1 −1
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ constructor(
     * Emits all notifications that are eligible to show as chips in the status bar. This is
     * different from which chips will *actually* show, see [shownNotificationChips] for that.
     */
    private val allNotificationChips: Flow<List<NotificationChipModel>> =
    val allNotificationChips: Flow<List<NotificationChipModel>> =
        if (StatusBarNotifChips.isEnabled) {
            // For all our current interactors...
            // TODO(b/364653005): When a promoted notification is added or removed, each individual
+44 −10
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.app.Notification;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -31,9 +32,14 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
import com.android.systemui.statusbar.notification.promoted.domain.interactor.PromotedNotificationsInteractor;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
import com.android.systemui.util.kotlin.JavaAdapterKt;

import com.google.common.primitives.Booleans;
import kotlinx.coroutines.CoroutineScope;

import java.util.Collections;
import java.util.List;

import javax.inject.Inject;

@@ -45,15 +51,31 @@ import javax.inject.Inject;
@CoordinatorScope
public class ColorizedFgsCoordinator implements Coordinator {
    private static final String TAG = "ColorizedCoordinator";
    private final PromotedNotificationsInteractor mPromotedNotificationsInteractor;
    private final CoroutineScope mMainScope;

    private List<String> mOrderedPromotedNotifKeys = Collections.emptyList();

    @Inject
    public ColorizedFgsCoordinator() {
    public ColorizedFgsCoordinator(
            @Application CoroutineScope mainScope,
            PromotedNotificationsInteractor promotedNotificationsInteractor
    ) {
        mPromotedNotificationsInteractor = promotedNotificationsInteractor;
        mMainScope = mainScope;
    }

    @Override
    public void attach(NotifPipeline pipeline) {
    public void attach(@NonNull NotifPipeline pipeline) {
        if (PromotedNotificationUi.isEnabled()) {
            pipeline.addPromoter(mPromotedOngoingPromoter);

            JavaAdapterKt.collectFlow(mMainScope,
                    mPromotedNotificationsInteractor.getOrderedChipNotificationKeys(),
                    (List<String> keys) -> {
                        mOrderedPromotedNotifKeys = keys;
                        mNotifSectioner.invalidateList("updated mOrderedPromotedNotifKeys");
                    });
        }
    }

@@ -82,12 +104,24 @@ public class ColorizedFgsCoordinator implements Coordinator {
            return false;
        }

        private NotifComparator mPreferPromoted = new NotifComparator("PreferPromoted") {
        /** get the sort key for any entry in the ongoing section */
        private int getSortKey(@Nullable NotificationEntry entry) {
            if (entry == null) return Integer.MAX_VALUE;
            // Order all promoted notif keys first, using their order in the list
            final int index = mOrderedPromotedNotifKeys.indexOf(entry.getKey());
            if (index >= 0) return index;
            // Next, prioritize promoted ongoing over other notifications
            return isPromotedOngoing(entry) ? Integer.MAX_VALUE - 1 : Integer.MAX_VALUE;
        }

        private final NotifComparator mOngoingComparator = new NotifComparator(
                "OngoingComparator") {
            @Override
            public int compare(@NonNull ListEntry o1, @NonNull ListEntry o2) {
                return -1 * Booleans.compare(
                        isPromotedOngoing(o1.getRepresentativeEntry()),
                        isPromotedOngoing(o2.getRepresentativeEntry()));
                return Integer.compare(
                        getSortKey(o1.getRepresentativeEntry()),
                        getSortKey(o2.getRepresentativeEntry())
                );
            }
        };

@@ -95,7 +129,7 @@ public class ColorizedFgsCoordinator implements Coordinator {
        @Override
        public NotifComparator getComparator() {
            if (PromotedNotificationUi.isEnabled()) {
                return mPreferPromoted;
                return mOngoingComparator;
            } else {
                return null;
            }
+3 −5
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -30,13 +29,12 @@ import kotlinx.coroutines.flow.map
class AODPromotedNotificationInteractor
@Inject
constructor(
    activeNotificationsInteractor: ActiveNotificationsInteractor,
    promotedNotificationsInteractor: PromotedNotificationsInteractor,
    dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
    /** The content to show as the promoted notification on AOD */
    val content: Flow<PromotedNotificationContentModel?> =
        activeNotificationsInteractor.topLevelRepresentativeNotifications.map { notifs ->
            notifs.firstNotNullOfOrNull { it.promotedContent }
        }
        promotedNotificationsInteractor.topPromotedNotificationContent

    val isPresent: Flow<Boolean> =
        content
Loading