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
This commit is contained in:
Julia Tuttle
2023-03-28 15:04:17 -04:00
parent 6fb979f488
commit d586650c85
3 changed files with 84 additions and 41 deletions

View File

@@ -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)
}
}

View File

@@ -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"
})
}

View File

@@ -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 setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should)
whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any()))
.thenReturn(should)
private fun setDefaultShouldHeadsUp(should: Boolean) {
whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(any()))
.thenReturn(DecisionImpl.of(should))
whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(any()))
.thenReturn(DecisionImpl.of(should))
}
private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) {
whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry))
.thenReturn(decision)
private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) {
whenever(visualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry))
.thenReturn(DecisionImpl.of(should))
whenever(visualInterruptionDecisionProvider.makeUnloggedHeadsUpDecision(entry))
.thenReturn(DecisionImpl.of(should))
}
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) {