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

Commit d586650c authored by Julia Tuttle's avatar Julia Tuttle
Browse files

Migrate HUN/FSI checks to new Provider

Update HeadsUpCoordinator and associated tests to use the new
VisualInterruptionDecisionProvider interface instead of the old
NotificationInterruptStateProvider one.

Bug: 261728888
Test: atest HeadsUpCoordinatorTest
Change-Id: I5db314c6b6d7c378cc88add64376d5bc077a659f
parent 6fb979f4
Loading
Loading
Loading
Loading
+31 −23
Original line number Diff line number Diff line
@@ -38,8 +38,7 @@ import com.android.systemui.statusbar.notification.collection.provider.LaunchFul
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -69,12 +68,12 @@ class HeadsUpCoordinator @Inject constructor(
    private val mSystemClock: SystemClock,
    private val mHeadsUpManager: HeadsUpManager,
    private val mHeadsUpViewBinder: HeadsUpViewBinder,
    private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
    private val mVisualInterruptionDecisionProvider: VisualInterruptionDecisionProvider,
    private val mRemoteInputManager: NotificationRemoteInputManager,
    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
    private val mFlags: NotifPipelineFlags,
    @IncomingHeader private val mIncomingHeaderController: NodeController,
    @Main private val mExecutor: DelayableExecutor,
    @Main private val mExecutor: DelayableExecutor
) : Coordinator {
    private val mEntriesBindingUntil = ArrayMap<String, Long>()
    private val mEntriesUpdateTimes = ArrayMap<String, Long>()
@@ -388,18 +387,21 @@ class HeadsUpCoordinator @Inject constructor(
        override fun onEntryAdded(entry: NotificationEntry) {
            // First check whether this notification should launch a full screen intent, and
            // launch it if needed.
            val fsiDecision = mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
            mNotificationInterruptStateProvider.logFullScreenIntentDecision(entry, fsiDecision)
            if (fsiDecision.shouldLaunch) {
            val fsiDecision =
                mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)
            mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(fsiDecision)
            if (fsiDecision.shouldInterrupt) {
                mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
            } else if (fsiDecision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
            } else if (fsiDecision.wouldInterruptWithoutDnd) {
                // If DND was the only reason this entry was suppressed, note it for potential
                // reconsideration on later ranking updates.
                addForFSIReconsideration(entry, mSystemClock.currentTimeMillis())
            }

            // shouldHeadsUp includes check for whether this notification should be filtered
            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
            // makeAndLogHeadsUpDecision includes check for whether this notification should be
            // filtered
            val shouldHeadsUpEver =
                mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
            mPostedEntries[entry.key] = PostedEntry(
                entry,
                wasAdded = true,
@@ -420,7 +422,8 @@ class HeadsUpCoordinator @Inject constructor(
         * up again.
         */
        override fun onEntryUpdated(entry: NotificationEntry) {
            val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
            val shouldHeadsUpEver =
                mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
            val shouldHeadsUpAgain = shouldHunAgain(entry)
            val isAlerting = mHeadsUpManager.isAlerting(entry.key)
            val isBinding = isEntryBinding(entry)
@@ -510,26 +513,26 @@ class HeadsUpCoordinator @Inject constructor(
                // If any of these entries are no longer suppressed, launch the FSI now.
                if (isCandidateForFSIReconsideration(entry)) {
                    val decision =
                        mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)
                    if (decision.shouldLaunch) {
                        mVisualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(
                            entry
                        )
                    if (decision.shouldInterrupt) {
                        // Log both the launch of the full screen and also that this was via a
                        // ranking update, and finally revoke candidacy for FSI reconsideration
                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.name)
                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
                            entry, decision)
                        mLogger.logEntryUpdatedToFullScreen(entry.key, decision.logReason)
                        mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision)
                        mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
                        mFSIUpdateCandidates.remove(entry.key)

                        // if we launch the FSI then this is no longer a candidate for HUN
                        continue
                    } else if (decision == FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) {
                    } else if (decision.wouldInterruptWithoutDnd) {
                        // decision has not changed; no need to log
                    } else {
                        // some other condition is now blocking FSI; log that and revoke candidacy
                        // for FSI reconsideration
                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.name)
                        mNotificationInterruptStateProvider.logFullScreenIntentDecision(
                            entry, decision)
                        mLogger.logEntryDisqualifiedFromFullScreen(entry.key, decision.logReason)
                        mVisualInterruptionDecisionProvider.logFullScreenIntentDecision(decision)
                        mFSIUpdateCandidates.remove(entry.key)
                    }
                }
@@ -539,13 +542,18 @@ class HeadsUpCoordinator @Inject constructor(
                //   state
                // - if it is present in PostedEntries and the previous state of shouldHeadsUp
                //   differs from the updated one
                val shouldHeadsUpEver = mNotificationInterruptStateProvider.checkHeadsUp(entry,
                                /* log= */ false)
                val decision =
                    mVisualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry)
                val shouldHeadsUpEver = decision.shouldInterrupt
                val postedShouldHeadsUpEver = mPostedEntries[entry.key]?.shouldHeadsUpEver ?: false
                val shouldUpdateEntry = postedShouldHeadsUpEver != shouldHeadsUpEver

                if (shouldUpdateEntry) {
                    mLogger.logEntryUpdatedByRanking(entry.key, shouldHeadsUpEver)
                    mLogger.logEntryUpdatedByRanking(
                        entry.key,
                        shouldHeadsUpEver,
                        decision.logReason
                    )
                    onEntryUpdated(entry)
                }
            }
+3 −2
Original line number Diff line number Diff line
@@ -61,12 +61,13 @@ class HeadsUpCoordinatorLogger constructor(
        })
    }

    fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean) {
    fun logEntryUpdatedByRanking(key: String, shouldHun: Boolean, reason: String) {
        buffer.log(TAG, LogLevel.DEBUG, {
            str1 = key
            bool1 = shouldHun
            str2 = reason
        }, {
            "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
            "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1 because $str2"
        })
    }

+49 −15
Original line number Diff line number Diff line
@@ -38,8 +38,10 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -52,6 +54,7 @@ import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import java.util.ArrayList
import java.util.function.Consumer
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -86,7 +89,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
    private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
    private val headsUpManager: HeadsUpManager = mock()
    private val headsUpViewBinder: HeadsUpViewBinder = mock()
    private val notificationInterruptStateProvider: NotificationInterruptStateProvider = mock()
    private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
    private val remoteInputManager: NotificationRemoteInputManager = mock()
    private val endLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
    private val headerController: NodeController = mock()
@@ -114,7 +117,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
            systemClock,
            headsUpManager,
            headsUpViewBinder,
            notificationInterruptStateProvider,
            visualInterruptionDecisionProvider,
            remoteInputManager,
            launchFullScreenIntentProvider,
            flags,
@@ -168,8 +171,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
        groupChild2 = helper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250)
        groupChild3 = helper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150)

        // Set the default HUN decision
        setDefaultShouldHeadsUp(false)

        // Set the default FSI decision
        setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
        setDefaultShouldFullScreen(FullScreenIntentDecision.NO_FULL_SCREEN_INTENT)
    }

    @Test
@@ -1006,31 +1012,59 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
        verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry)
    }

    private fun setDefaultShouldHeadsUp(should: Boolean) {
        whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(any()))
            .thenReturn(DecisionImpl.of(should))
        whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(any()))
            .thenReturn(DecisionImpl.of(should))
    }

    private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
        whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
        whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
                .thenReturn(should)
        whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry))
            .thenReturn(DecisionImpl.of(should))
        whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry))
            .thenReturn(DecisionImpl.of(should))
    }

    private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
        whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry))
            .thenReturn(decision)
    private fun setDefaultShouldFullScreen(
        originalDecision: FullScreenIntentDecision
    ) {
        val provider = visualInterruptionDecisionProvider
        whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer {
            val entry: NotificationEntry = it.getArgument(0)
            FullScreenIntentDecisionImpl(entry, originalDecision)
        }
    }

    private fun setShouldFullScreen(
        entry: NotificationEntry,
        originalDecision: FullScreenIntentDecision
    ) {
        whenever(
            visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)
        ).thenAnswer {
            FullScreenIntentDecisionImpl(entry, originalDecision)
        }
    }

    private fun verifyLoggedFullScreenIntentDecision(
        entry: NotificationEntry,
        decision: FullScreenIntentDecision
        originalDecision: FullScreenIntentDecision
    ) {
        verify(notificationInterruptStateProvider).logFullScreenIntentDecision(entry, decision)
        val decision = withArgCaptor {
            verify(visualInterruptionDecisionProvider).logFullScreenIntentDecision(capture())
        }
        check(decision is FullScreenIntentDecisionImpl)
        assertEquals(entry, decision.originalEntry)
        assertEquals(originalDecision, decision.originalDecision)
    }

    private fun verifyNoFullScreenIntentDecisionLogged() {
        verify(notificationInterruptStateProvider, never())
            .logFullScreenIntentDecision(any(), any())
        verify(visualInterruptionDecisionProvider, never()).logFullScreenIntentDecision(any())
    }

    private fun clearInterruptionProviderInvocations() {
        clearInvocations(notificationInterruptStateProvider)
        clearInvocations(visualInterruptionDecisionProvider)
    }

    private fun finishBind(entry: NotificationEntry) {