Merge "Add an enum for decisions about whether to launch FSI." into tm-qpr-dev

This commit is contained in:
Yuri Lin
2022-12-12 17:34:42 +00:00
committed by Android (Google) Code Review
3 changed files with 266 additions and 36 deletions

View File

@@ -24,6 +24,79 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
* When a notification is heads-up when dozing, this is also called "pulsing."
*/
public interface NotificationInterruptStateProvider {
/**
* Enum representing a decision of whether to show a full screen intent. While many of the
* relevant properties could overlap, the decision represents the deciding factor for whether
* the full screen intent should or shouldn't launch.
*/
enum FullScreenIntentDecision {
/**
* No full screen intent included, so there is nothing to show.
*/
NO_FULL_SCREEN_INTENT(false),
/**
* Suppressed by DND settings.
*/
NO_FSI_SUPPRESSED_BY_DND(false),
/**
* Full screen intent was suppressed *only* by DND, and if not for DND would have shown. We
* track this separately in order to allow the intent to be shown if the DND decision
* changes.
*/
NO_FSI_SUPPRESSED_ONLY_BY_DND(false),
/**
* Notification importance not high enough to show FSI.
*/
NO_FSI_NOT_IMPORTANT_ENOUGH(false),
/**
* Notification should not FSI due to having suppressive GroupAlertBehavior. This blocks a
* potentially malicious use of flags that previously allowed apps to escalate a HUN to an
* FSI even while the device was unlocked.
*/
NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false),
/**
* Device screen is off, so the FSI should launch.
*/
FSI_DEVICE_NOT_INTERACTIVE(true),
/**
* Device is currently dreaming, so FSI should launch.
*/
FSI_DEVICE_IS_DREAMING(true),
/**
* Keyguard is showing, so FSI should launch.
*/
FSI_KEYGUARD_SHOWING(true),
/**
* The notification is expected to show heads-up, so FSI is not needed.
*/
NO_FSI_EXPECTED_TO_HUN(false),
/**
* The notification is not expected to HUN while the keyguard is occluded, so show FSI.
*/
FSI_KEYGUARD_OCCLUDED(true),
/**
* The notification is not expected to HUN when the keyguard is showing but not occluded,
* which likely means that the shade is showing over the lockscreen; show FSI in this case.
*/
FSI_LOCKED_SHADE(true),
/**
* FSI requires keyguard to be showing, but there is no keyguard. This is a (potentially
* malicious) warning state where we suppress the FSI because the device is in use knowing
* that the HUN will probably not display.
*/
NO_FSI_NO_HUN_OR_KEYGUARD(false),
/**
* No conditions blocking FSI launch.
*/
FSI_EXPECTED_NOT_TO_HUN(true);
public final boolean shouldLaunch;
FullScreenIntentDecision(boolean shouldLaunch) {
this.shouldLaunch = shouldLaunch;
}
}
/**
* If the device is awake (not dozing):
* Whether the notification should peek in from the top and alert the user.
@@ -65,6 +138,27 @@ public interface NotificationInterruptStateProvider {
*/
boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry);
/**
* Whether an entry's full screen intent would be launched.
*
* This method differs from shouldLaunchFullScreenIntentWhenAdded by returning more information
* on the decision, and only optionally logging the outcome. It should be used in cases where
* the caller needs to know what the decision would be, but may not actually launch the full
* screen intent.
*
* @param entry the entry to evaluate
* @return FullScreenIntentDecision representing the decision for whether to show the intent
*/
FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry);
/**
* Write the full screen launch decision for the given entry to logs.
*
* @param entry the NotificationEntry for which the decision applies
* @param decision reason for launch or no-launch of FSI for entry
*/
void logFullScreenIntentDecision(NotificationEntry entry, FullScreenIntentDecision decision);
/**
* Add a component that can suppress visual interruptions.
*/

View File

@@ -211,20 +211,49 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
*/
@Override
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
if (entry.getSbn().getNotification().fullScreenIntent == null) {
return false;
FullScreenIntentDecision decision = getFullScreenIntentDecision(entry);
logFullScreenIntentDecision(entry, decision);
return decision.shouldLaunch;
}
// Given whether the relevant entry was suppressed by DND, and the full screen intent launch
// decision independent of the DND decision, returns the combined FullScreenIntentDecision that
// results. If the entry was suppressed by DND but the decision otherwise would launch the
// FSI, then it is suppressed *only* by DND, whereas (because the DND decision happens before
// all others) if the entry would not otherwise have launched the FSI, DND is the effective
// suppressor.
//
// If the entry was not suppressed by DND, just returns the given decision.
private FullScreenIntentDecision getDecisionGivenSuppression(FullScreenIntentDecision decision,
boolean suppressedByDND) {
if (suppressedByDND) {
return decision.shouldLaunch
? FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
: FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND;
}
return decision;
}
@Override
public FullScreenIntentDecision getFullScreenIntentDecision(NotificationEntry entry) {
if (entry.getSbn().getNotification().fullScreenIntent == null) {
return FullScreenIntentDecision.NO_FULL_SCREEN_INTENT;
}
// Boolean indicating whether this FSI would have been suppressed by DND. Because we
// want to be able to identify when something would have shown an FSI if not for being
// suppressed, we need to keep track of this value for future decisions.
boolean suppressedByDND = false;
// Never show FSI when suppressed by DND
if (entry.shouldSuppressFullScreenIntent()) {
mLogger.logNoFullscreen(entry, "Suppressed by DND");
return false;
suppressedByDND = true;
}
// Never show FSI if importance is not HIGH
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
mLogger.logNoFullscreen(entry, "Not important enough");
return false;
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH,
suppressedByDND);
}
// If the notification has suppressive GroupAlertBehavior, block FSI and warn.
@@ -232,36 +261,35 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
// b/231322873: Detect and report an event when a notification has both an FSI and a
// suppressive groupAlertBehavior, and now correctly block the FSI from firing.
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
return false;
return getDecisionGivenSuppression(
FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
suppressedByDND);
}
// If the screen is off, then launch the FullScreenIntent
if (!mPowerManager.isInteractive()) {
mLogger.logFullscreen(entry, "Device is not interactive");
return true;
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE,
suppressedByDND);
}
// If the device is currently dreaming, then launch the FullScreenIntent
if (isDreaming()) {
mLogger.logFullscreen(entry, "Device is dreaming");
return true;
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING,
suppressedByDND);
}
// If the keyguard is showing, then launch the FullScreenIntent
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
mLogger.logFullscreen(entry, "Keyguard is showing");
return true;
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_KEYGUARD_SHOWING,
suppressedByDND);
}
// If the notification should HUN, then we don't need FSI
if (shouldHeadsUp(entry)) {
mLogger.logNoFullscreen(entry, "Expected to HUN");
return false;
// Because this is not the heads-up decision-making point, and checking whether it would
// HUN, don't log this specific check.
if (checkHeadsUp(entry, /* log= */ false)) {
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN,
suppressedByDND);
}
// Check whether FSI requires the keyguard to be showing.
@@ -270,27 +298,77 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// If notification won't HUN and keyguard is showing, launch the FSI.
if (mKeyguardStateController.isShowing()) {
if (mKeyguardStateController.isOccluded()) {
mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded");
return getDecisionGivenSuppression(
FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED,
suppressedByDND);
} else {
// Likely LOCKED_SHADE, but launch FSI anyway
mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_LOCKED_SHADE,
suppressedByDND);
}
return true;
}
// Detect the case determined by b/231322873 to launch FSI while device is in use,
// as blocked by the correct implementation, and report the event.
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
return false;
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD,
suppressedByDND);
}
// If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
mLogger.logFullscreen(entry, "Expected not to HUN");
return true;
return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN,
suppressedByDND);
}
@Override
public void logFullScreenIntentDecision(NotificationEntry entry,
FullScreenIntentDecision decision) {
final int uid = entry.getSbn().getUid();
final String packageName = entry.getSbn().getPackageName();
switch (decision) {
case NO_FULL_SCREEN_INTENT:
return;
case NO_FSI_SUPPRESSED_BY_DND:
case NO_FSI_SUPPRESSED_ONLY_BY_DND:
mLogger.logNoFullscreen(entry, "Suppressed by DND");
return;
case NO_FSI_NOT_IMPORTANT_ENOUGH:
mLogger.logNoFullscreen(entry, "Not important enough");
return;
case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"groupAlertBehavior");
mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
packageName);
mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
return;
case FSI_DEVICE_NOT_INTERACTIVE:
mLogger.logFullscreen(entry, "Device is not interactive");
return;
case FSI_DEVICE_IS_DREAMING:
mLogger.logFullscreen(entry, "Device is dreaming");
return;
case FSI_KEYGUARD_SHOWING:
mLogger.logFullscreen(entry, "Keyguard is showing");
return;
case NO_FSI_EXPECTED_TO_HUN:
mLogger.logNoFullscreen(entry, "Expected to HUN");
return;
case FSI_KEYGUARD_OCCLUDED:
mLogger.logFullscreen(entry,
"Expected not to HUN while keyguard occluded");
return;
case FSI_LOCKED_SHADE:
mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
return;
case NO_FSI_NO_HUN_OR_KEYGUARD:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"no hun or keyguard");
mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
return;
case FSI_EXPECTED_NOT_TO_HUN:
mLogger.logFullscreen(entry, "Expected not to HUN");
}
}
private boolean isDreaming() {

View File

@@ -23,6 +23,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -61,6 +62,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -527,6 +529,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FULL_SCREEN_INTENT);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -534,6 +538,44 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
verify(mLogger, never()).logFullscreen(any(), any());
}
@Test
public void testShouldNotFullScreen_suppressedOnlyByDND() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
modifyRanking(entry)
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
.build();
when(mPowerManager.isInteractive()).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger, never()).logFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
}
@Test
public void testShouldNotFullScreen_suppressedByDNDAndOther() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_LOW, /* silenced */ false);
modifyRanking(entry)
.setSuppressedVisualEffects(SUPPRESSED_EFFECT_FULL_SCREEN_INTENT)
.build();
when(mPowerManager.isInteractive()).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger, never()).logFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger).logNoFullscreen(entry, "Suppressed by DND");
}
@Test
public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
@@ -547,6 +589,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger).logNoFullscreen(entry, "Not important enough");
@@ -567,6 +611,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(true);
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -594,6 +640,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -614,6 +662,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(true);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_DEVICE_IS_DREAMING);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -634,6 +684,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_SHOWING);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger, never()).logNoFullscreen(any(), any());
@@ -655,6 +707,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mDreamManager.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
@@ -671,9 +725,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(SHADE);
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger).logFullscreen(entry, "Expected not to HUN");
@@ -691,9 +746,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isOccluded()).thenReturn(true);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
@@ -711,9 +767,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.FSI_LOCKED_SHADE);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isTrue();
verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
@@ -731,9 +788,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
when(mKeyguardStateController.isShowing()).thenReturn(false);
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
.isEqualTo(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
verify(mLogger, never()).logNoFullscreen(any(), any());
verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
verify(mLogger, never()).logFullscreen(any(), any());