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

Commit f3007b81 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Add RowAlertTimeCoordinator to ensure group summaries have an alerting bell

Bug: 326348484
Test: atest RowAlertTimeCoordinatorTest
Flag: None
Change-Id: If09440d2a7cbf6a38768fc90a03a51e8265ccf2d
parent cd258079
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ class NotifCoordinatorsImpl @Inject constructor(
        mediaCoordinator: MediaCoordinator,
        preparationCoordinator: PreparationCoordinator,
        remoteInputCoordinator: RemoteInputCoordinator,
        rowAlertTimeCoordinator: RowAlertTimeCoordinator,
        rowAppearanceCoordinator: RowAppearanceCoordinator,
        stackCoordinator: StackCoordinator,
        shadeEventCoordinator: ShadeEventCoordinator,
@@ -69,9 +70,7 @@ class NotifCoordinatorsImpl @Inject constructor(
    private val mCoordinators: MutableList<Coordinator> = ArrayList()
    private val mOrderedSections: MutableList<NotifSectioner> = ArrayList()

    /**
     * Creates all the coordinators.
     */
    /** Creates all the coordinators. */
    init {
        // Attach core coordinators.
        mCoreCoordinators.add(dataStoreCoordinator)
@@ -89,6 +88,7 @@ class NotifCoordinatorsImpl @Inject constructor(
        mCoordinators.add(groupCountCoordinator)
        mCoordinators.add(groupWhenCoordinator)
        mCoordinators.add(mediaCoordinator)
        mCoordinators.add(rowAlertTimeCoordinator)
        mCoordinators.add(rowAppearanceCoordinator)
        mCoordinators.add(stackCoordinator)
        mCoordinators.add(shadeEventCoordinator)
+61 −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.util.ArrayMap
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import javax.inject.Inject
import kotlin.math.max

/**
 * A small coordinator which ensures the "alerted" bell shows not just for recently alerted entries,
 * but also on the summary for every such entry.
 */
@CoordinatorScope
class RowAlertTimeCoordinator @Inject constructor() : Coordinator {

    private val latestAlertTimeBySummary = ArrayMap<NotificationEntry, Long>()

    override fun attach(pipeline: NotifPipeline) {
        pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
        pipeline.addOnAfterRenderEntryListener(::onAfterRenderEntry)
    }

    private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
        latestAlertTimeBySummary.clear()
        entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
            val summary = checkNotNull(groupEntry.summary)
            latestAlertTimeBySummary[summary] = groupEntry.calculateLatestAlertTime()
        }
    }

    private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
        // Show the "alerted" bell icon based on the latest group member for summaries
        val lastAudiblyAlerted = latestAlertTimeBySummary[entry] ?: entry.lastAudiblyAlertedMs
        controller.setLastAudibleMs(lastAudiblyAlerted)
    }

    private fun GroupEntry.calculateLatestAlertTime(): Long {
        val lastChildAlertedTime = children.maxOf { it.lastAudiblyAlertedMs }
        val summaryAlertedTime = checkNotNull(summary).lastAudiblyAlertedMs
        return max(lastChildAlertedTime, summaryAlertedTime)
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -75,7 +75,5 @@ class RowAppearanceCoordinator @Inject internal constructor(
                (mAutoExpandFirstNotification && entry == entryToExpand))
        // Show/hide the feedback icon
        controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
        // Show the "alerted" bell icon
        controller.setLastAudibleMs(entry.lastAudiblyAlertedMs)
    }
}
+99 −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.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
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.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks

@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class RowAlertTimeCoordinatorTest : SysuiTestCase() {
    private lateinit var coordinator: RowAlertTimeCoordinator
    private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
    private lateinit var afterRenderEntryListener: OnAfterRenderEntryListener

    @Mock private lateinit var pipeline: NotifPipeline

    @Before
    fun setUp() {
        initMocks(this)
        coordinator = RowAlertTimeCoordinator()
        coordinator.attach(pipeline)
        beforeFinalizeFilterListener = withArgCaptor {
            verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
        }
        afterRenderEntryListener = withArgCaptor {
            verify(pipeline).addOnAfterRenderEntryListener(capture())
        }
    }

    @Test
    fun testSetLastAudiblyAlerted() {
        val entry1 = NotificationEntryBuilder().setLastAudiblyAlertedMs(10).build()
        val entry2 = NotificationEntryBuilder().setLastAudiblyAlertedMs(20).build()
        val summary = NotificationEntryBuilder().setLastAudiblyAlertedMs(5).build()
        val child1 = NotificationEntryBuilder().setLastAudiblyAlertedMs(0).build()
        val child2 = NotificationEntryBuilder().setLastAudiblyAlertedMs(8).build()
        val group =
            GroupEntryBuilder()
                .setKey("group")
                .setSummary(summary)
                .addChild(child1)
                .addChild(child2)
                .build()

        val entries = listOf(entry1, summary, child1, child2, entry2)

        beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry1, group, entry2))
        val actualTimesSet =
            entries.associateWith {
                val rowController = mock<NotifRowController>()
                afterRenderEntryListener.onAfterRenderEntry(it, rowController)
                withArgCaptor<Long> {
                    verify(rowController).setLastAudibleMs(capture())
                    verifyNoMoreInteractions(rowController)
                }
            }
        val expectedTimesSet =
            mapOf(
                entry1 to 10L,
                entry2 to 20L,
                summary to 8L,
                child1 to 0L,
                child2 to 8L,
            )
        assertThat(actualTimesSet).containsExactlyEntriesIn(expectedTimesSet)
    }
}
+1 −7
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
            verify(pipeline).addOnAfterRenderEntryListener(capture())
        }
        whenever(assistantFeedbackController.getFeedbackIcon(any())).thenReturn(FeedbackIcon(1, 2))
        entry1 = NotificationEntryBuilder().setSection(section1).setLastAudiblyAlertedMs(17).build()
        entry1 = NotificationEntryBuilder().setSection(section1).build()
        entry2 = NotificationEntryBuilder().setSection(section2).build()
    }

@@ -102,12 +102,6 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
        verify(controller2).setSystemExpanded(eq(false))
    }

    @Test
    fun testSetLastAudiblyAlerted() {
        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
        verify(controller1).setLastAudibleMs(eq(17.toLong()))
    }

    @Test
    fun testSetFeedbackIcon() {
        afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)