Merge changes from topic "interruptions-refactor-1" into udc-dev

* changes:
  Migrate Bubble checks to new Provider
  Migrate HUN/FSI checks to new Provider
  Add logReason to visual interruption decisions
This commit is contained in:
Julia Tuttle
2023-04-20 15:55:08 +00:00
committed by Android (Google) Code Review
8 changed files with 109 additions and 62 deletions

View File

@@ -288,7 +288,7 @@ public abstract class SystemUIModule {
INotificationManager notificationManager,
IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
CommonNotifCollection notifCollection,
@@ -306,7 +306,7 @@ public abstract class SystemUIModule {
notificationManager,
dreamManager,
visibilityProvider,
interruptionStateProvider,
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
notifCollection,

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

@@ -36,6 +36,8 @@ class NotificationInterruptStateProviderWrapper(
SHOULD_INTERRUPT(shouldInterrupt = true),
SHOULD_NOT_INTERRUPT(shouldInterrupt = false);
override val logReason = "unknown"
companion object {
fun of(booleanDecision: Boolean) =
if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
@@ -49,6 +51,7 @@ class NotificationInterruptStateProviderWrapper(
) : FullScreenIntentDecision {
override val shouldInterrupt = originalDecision.shouldLaunch
override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
override val logReason = originalDecision.name
}
override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {

View File

@@ -32,9 +32,12 @@ interface VisualInterruptionDecisionProvider {
* full-screen intent decisions.
*
* @property[shouldInterrupt] whether a visual interruption should be triggered
* @property[logReason] a log-friendly string explaining the reason for the decision; should be
* used *only* for logging, not decision-making
*/
interface Decision {
val shouldInterrupt: Boolean
val logReason: String
}
/**

View File

@@ -66,7 +66,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -100,7 +100,7 @@ public class BubblesManager {
private final INotificationManager mNotificationManager;
private final IDreamManager mDreamManager;
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
@@ -126,7 +126,7 @@ public class BubblesManager {
INotificationManager notificationManager,
IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
CommonNotifCollection notifCollection,
@@ -145,7 +145,7 @@ public class BubblesManager {
notificationManager,
dreamManager,
visibilityProvider,
interruptionStateProvider,
visualInterruptionDecisionProvider,
zenModeController,
notifUserManager,
notifCollection,
@@ -169,7 +169,7 @@ public class BubblesManager {
INotificationManager notificationManager,
IDreamManager dreamManager,
NotificationVisibilityProvider visibilityProvider,
NotificationInterruptStateProvider interruptionStateProvider,
VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
CommonNotifCollection notifCollection,
@@ -185,7 +185,7 @@ public class BubblesManager {
mNotificationManager = notificationManager;
mDreamManager = dreamManager;
mVisibilityProvider = visibilityProvider;
mNotificationInterruptStateProvider = interruptionStateProvider;
mVisualInterruptionDecisionProvider = visualInterruptionDecisionProvider;
mNotifUserManager = notifUserManager;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
@@ -272,7 +272,7 @@ public class BubblesManager {
for (NotificationEntry entry : activeEntries) {
if (mNotifUserManager.isCurrentProfile(entry.getSbn().getUserId())
&& savedBubbleKeys.contains(entry.getKey())
&& mNotificationInterruptStateProvider.shouldBubbleUp(entry)
&& shouldBubbleUp(entry)
&& entry.isBubble()) {
result.add(notifToBubbleEntry(entry));
}
@@ -416,16 +416,13 @@ public class BubblesManager {
}
void onEntryAdded(NotificationEntry entry) {
if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
&& entry.isBubble()) {
if (shouldBubbleUp(entry) && entry.isBubble()) {
mBubbles.onEntryAdded(notifToBubbleEntry(entry));
}
}
void onEntryUpdated(NotificationEntry entry, boolean fromSystem) {
boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry);
mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
shouldBubble, fromSystem);
mBubbles.onEntryUpdated(notifToBubbleEntry(entry), shouldBubbleUp(entry), fromSystem);
}
void onEntryRemoved(NotificationEntry entry) {
@@ -438,12 +435,8 @@ public class BubblesManager {
for (int i = 0; i < orderedKeys.length; i++) {
String key = orderedKeys[i];
final NotificationEntry entry = mCommonNotifCollection.getEntry(key);
BubbleEntry bubbleEntry = entry != null
? notifToBubbleEntry(entry)
: null;
boolean shouldBubbleUp = entry != null
? mNotificationInterruptStateProvider.shouldBubbleUp(entry)
: false;
BubbleEntry bubbleEntry = entry != null ? notifToBubbleEntry(entry) : null;
boolean shouldBubbleUp = entry != null ? shouldBubbleUp(entry) : false;
pendingOrActiveNotif.put(key, new Pair<>(bubbleEntry, shouldBubbleUp));
}
mBubbles.onRankingUpdated(rankingMap, pendingOrActiveNotif);
@@ -637,6 +630,10 @@ public class BubblesManager {
}
}
private boolean shouldBubbleUp(NotificationEntry e) {
return mVisualInterruptionDecisionProvider.makeAndLogBubbleDecision(e).getShouldInterrupt();
}
/**
* Callback for when the BubbleController wants to interact with the notification pipeline to:
* - Remove a previously bubbled notification

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) {

View File

@@ -115,6 +115,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -398,7 +399,7 @@ public class BubblesTest extends SysuiTestCase {
mock(INotificationManager.class),
mIDreamManager,
mVisibilityProvider,
interruptionStateProvider,
new NotificationInterruptStateProviderWrapper(interruptionStateProvider),
mZenModeController,
mLockscreenUserManager,
mCommonNotifCollection,