Block FullScreenIntent while device is in use if notification has a silencing GroupAlertBehavior.
Bug: 231322873 Test: atest NotificationInterruptStateProviderImplTest Merged-In: Id82d20c9f1f2001400871b5381f52b40fbdf81c5 Change-Id: Id82d20c9f1f2001400871b5381f52b40fbdf81c5
This commit is contained in:
@@ -20,8 +20,10 @@ import android.service.notification.StatusBarNotification
|
|||||||
import com.android.systemui.log.LogBuffer
|
import com.android.systemui.log.LogBuffer
|
||||||
import com.android.systemui.log.LogLevel.DEBUG
|
import com.android.systemui.log.LogLevel.DEBUG
|
||||||
import com.android.systemui.log.LogLevel.INFO
|
import com.android.systemui.log.LogLevel.INFO
|
||||||
|
import com.android.systemui.log.LogLevel.WARNING
|
||||||
import com.android.systemui.log.dagger.NotificationHeadsUpLog
|
import com.android.systemui.log.dagger.NotificationHeadsUpLog
|
||||||
import com.android.systemui.log.dagger.NotificationLog
|
import com.android.systemui.log.dagger.NotificationLog
|
||||||
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NotificationInterruptLogger @Inject constructor(
|
class NotificationInterruptLogger @Inject constructor(
|
||||||
@@ -212,6 +214,33 @@ class NotificationInterruptLogger @Inject constructor(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun logNoFullscreen(entry: NotificationEntry, reason: String) {
|
||||||
|
hunBuffer.log(TAG, DEBUG, {
|
||||||
|
str1 = entry.key
|
||||||
|
str2 = reason
|
||||||
|
}, {
|
||||||
|
"No FullScreenIntent: $str2: $str1"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logNoFullscreenWarning(entry: NotificationEntry, reason: String) {
|
||||||
|
hunBuffer.log(TAG, WARNING, {
|
||||||
|
str1 = entry.key
|
||||||
|
str2 = reason
|
||||||
|
}, {
|
||||||
|
"No FullScreenIntent: WARNING: $str2: $str1"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logFullscreen(entry: NotificationEntry, reason: String) {
|
||||||
|
hunBuffer.log(TAG, DEBUG, {
|
||||||
|
str1 = entry.key
|
||||||
|
str2 = reason
|
||||||
|
}, {
|
||||||
|
"FullScreenIntent: $str2: $str1"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun keyguardHideNotification(key: String) {
|
fun keyguardHideNotification(key: String) {
|
||||||
hunBuffer.log(TAG, DEBUG, {
|
hunBuffer.log(TAG, DEBUG, {
|
||||||
str1 = key
|
str1 = key
|
||||||
|
|||||||
@@ -177,9 +177,69 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
|
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
|
||||||
return entry.getSbn().getNotification().fullScreenIntent != null
|
if (entry.getSbn().getNotification().fullScreenIntent == null) {
|
||||||
&& (!shouldHeadsUp(entry)
|
return false;
|
||||||
|| mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
|
}
|
||||||
|
|
||||||
|
// Never show FSI when suppressed by DND
|
||||||
|
if (entry.shouldSuppressFullScreenIntent()) {
|
||||||
|
mLogger.logNoFullscreen(entry, "Suppressed by DND");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never show FSI if importance is not HIGH
|
||||||
|
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
|
||||||
|
mLogger.logNoFullscreen(entry, "Not important enough");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the notification has suppressive GroupAlertBehavior, block FSI and warn.
|
||||||
|
StatusBarNotification sbn = entry.getSbn();
|
||||||
|
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();
|
||||||
|
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
|
||||||
|
mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the screen is off, then launch the FullScreenIntent
|
||||||
|
if (!mPowerManager.isInteractive()) {
|
||||||
|
mLogger.logFullscreen(entry, "Device is not interactive");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the device is currently dreaming, then launch the FullScreenIntent
|
||||||
|
if (isDreaming()) {
|
||||||
|
mLogger.logFullscreen(entry, "Device is dreaming");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the keyguard is showing, then launch the FullScreenIntent
|
||||||
|
if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
|
||||||
|
mLogger.logFullscreen(entry, "Keyguard is showing");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the notification should HUN, then we don't need FSI
|
||||||
|
if (shouldHeadsUp(entry)) {
|
||||||
|
mLogger.logNoFullscreen(entry, "Expected to HUN");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
|
||||||
|
mLogger.logFullscreen(entry, "Expected not to HUN");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDreaming() {
|
||||||
|
try {
|
||||||
|
return mDreamManager.isDreaming();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Failed to query dream manager.", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
|
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
|
||||||
@@ -219,13 +279,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isDreaming = false;
|
boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
|
||||||
try {
|
|
||||||
isDreaming = mDreamManager.isDreaming();
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.e(TAG, "Failed to query dream manager.", e);
|
|
||||||
}
|
|
||||||
boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
|
|
||||||
|
|
||||||
if (!inUse) {
|
if (!inUse) {
|
||||||
mLogger.logNoHeadsUpNotInUse(sbn);
|
mLogger.logNoHeadsUpNotInUse(sbn);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
|
|||||||
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
|
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
|
||||||
|
|
||||||
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
|
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
|
||||||
|
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
|
||||||
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
@@ -31,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -95,6 +97,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
|
|||||||
NotifPipelineFlags mFlags;
|
NotifPipelineFlags mFlags;
|
||||||
@Mock
|
@Mock
|
||||||
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
|
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
|
||||||
|
@Mock
|
||||||
|
PendingIntent mPendingIntent;
|
||||||
|
|
||||||
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
|
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
|
||||||
|
|
||||||
@@ -422,6 +426,122 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
|
|||||||
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
|
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
|
||||||
|
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isFalse();
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger, never()).logFullscreen(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isFalse();
|
||||||
|
verify(mLogger).logNoFullscreen(entry, "Not important enough");
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger, never()).logFullscreen(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(false);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(true);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isFalse();
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
|
||||||
|
verify(mLogger, never()).logFullscreen(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldFullScreen_notInteractive() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(false);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isTrue();
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger).logFullscreen(entry, "Device is not interactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldFullScreen_isDreaming() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(true);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isTrue();
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger).logFullscreen(entry, "Device is dreaming");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldFullScreen_onKeyguard() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isTrue();
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger).logFullscreen(entry, "Keyguard is showing");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldNotFullScreen_willHun() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isFalse();
|
||||||
|
verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger, never()).logFullscreen(any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShouldFullScreen_packageSnoozed() throws RemoteException {
|
||||||
|
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
|
||||||
|
when(mPowerManager.isInteractive()).thenReturn(true);
|
||||||
|
when(mPowerManager.isScreenOn()).thenReturn(true);
|
||||||
|
when(mDreamManager.isDreaming()).thenReturn(false);
|
||||||
|
when(mStatusBarStateController.getState()).thenReturn(SHADE);
|
||||||
|
when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
|
||||||
|
|
||||||
|
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
|
||||||
|
.isTrue();
|
||||||
|
verify(mLogger).logNoHeadsUpPackageSnoozed(entry.getSbn());
|
||||||
|
verify(mLogger, never()).logNoFullscreen(any(), any());
|
||||||
|
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
|
||||||
|
verify(mLogger).logFullscreen(entry, "Expected not to HUN");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bubbles can happen.
|
* Bubbles can happen.
|
||||||
*/
|
*/
|
||||||
@@ -526,6 +646,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
|
|||||||
.setContentText("content text")
|
.setContentText("content text")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
return createNotification(importance, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationEntry createNotification(int importance, Notification n) {
|
||||||
return new NotificationEntryBuilder()
|
return new NotificationEntryBuilder()
|
||||||
.setPkg("a")
|
.setPkg("a")
|
||||||
.setOpPkg("a")
|
.setOpPkg("a")
|
||||||
@@ -536,6 +660,20 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NotificationEntry createFsiNotification(int importance, boolean silent) {
|
||||||
|
Notification n = new Notification.Builder(getContext(), "a")
|
||||||
|
.setContentTitle("title")
|
||||||
|
.setContentText("content text")
|
||||||
|
.setFullScreenIntent(mPendingIntent, true)
|
||||||
|
.setGroup("fsi")
|
||||||
|
.setGroupAlertBehavior(silent
|
||||||
|
? Notification.GROUP_ALERT_SUMMARY
|
||||||
|
: Notification.GROUP_ALERT_ALL)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return createNotification(importance, n);
|
||||||
|
}
|
||||||
|
|
||||||
private final NotificationInterruptSuppressor
|
private final NotificationInterruptSuppressor
|
||||||
mSuppressAwakeHeadsUp =
|
mSuppressAwakeHeadsUp =
|
||||||
new NotificationInterruptSuppressor() {
|
new NotificationInterruptSuppressor() {
|
||||||
|
|||||||
Reference in New Issue
Block a user