diff options
2 files changed, 111 insertions, 10 deletions
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index a78b3a2e0c61..13429db47ebd 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -22,12 +22,14 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import android.Manifest.permission; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -223,7 +225,10 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); return new StrategyAvalanche( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), @@ -231,14 +236,17 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT), - appStrategy); + appStrategy, appStrategy.mExemptionProvider); } else { return new StrategyPerApp( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); } } @@ -1098,6 +1106,11 @@ public final class NotificationAttentionHelper { } } + // Returns true if a notification should be exempted from attenuation + private interface ExemptionProvider { + boolean isExempted(NotificationRecord record); + } + @VisibleForTesting abstract static class PolitenessStrategy { static final int POLITE_STATE_DEFAULT = 0; @@ -1128,8 +1141,10 @@ public final class NotificationAttentionHelper { protected boolean mIsActive = true; + protected final ExemptionProvider mExemptionProvider; + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted) { + int volumeMuted, ExemptionProvider exemptionProvider) { mVolumeStates = new HashMap<>(); mLastUpdatedTimestampByPackage = new HashMap<>(); @@ -1137,6 +1152,7 @@ public final class NotificationAttentionHelper { this.mTimeoutMuted = timeoutMuted; this.mVolumePolite = volumePolite / 100.0f; this.mVolumeMuted = volumeMuted / 100.0f; + this.mExemptionProvider = exemptionProvider; } abstract void onNotificationPosted(NotificationRecord record); @@ -1294,8 +1310,8 @@ public final class NotificationAttentionHelper { private final int mMaxPostedForReset; public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int maxPosted) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mNumPosted = new HashMap<>(); mMaxPostedForReset = maxPosted; @@ -1316,7 +1332,12 @@ public final class NotificationAttentionHelper { final String key = getChannelKey(record); @PolitenessState final int currState = getPolitenessState(record); - @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); + @PolitenessState int nextState; + if (Flags.politeNotificationsAttnUpdate()) { + nextState = getNextState(currState, timeSinceLastNotif, record); + } else { + nextState = getNextState(currState, timeSinceLastNotif); + } // Reset to default state if number of posted notifications exceed this value when muted int numPosted = mNumPosted.getOrDefault(key, 0) + 1; @@ -1334,6 +1355,14 @@ public final class NotificationAttentionHelper { mVolumeStates.put(key, nextState); } + @PolitenessState int getNextState(@PolitenessState final int currState, + final long timeSinceLastNotif, final NotificationRecord record) { + if (mExemptionProvider.isExempted(record)) { + return POLITE_STATE_DEFAULT; + } + return getNextState(currState, timeSinceLastNotif); + } + @Override public void onUserInteraction(final NotificationRecord record) { super.onUserInteraction(record); @@ -1354,8 +1383,9 @@ public final class NotificationAttentionHelper { private long mLastAvalancheTriggerTimestamp = 0; StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy, + ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mTimeoutAvalanche = timeoutAvalanche; mAppStrategy = appStrategy; @@ -1528,7 +1558,7 @@ public final class NotificationAttentionHelper { return true; } - return false; + return mExemptionProvider.isExempted(record); } private boolean isAvalancheExempted(final NotificationRecord record) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index a3d57c3df51e..70a003814036 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -24,6 +24,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; @@ -52,6 +54,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -190,6 +193,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { getContext().addMockSystemService(Vibrator.class, mVibrator); getContext().addMockSystemService(PackageManager.class, mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false); + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + anyString())).thenReturn(PERMISSION_DENIED); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); @@ -2363,6 +2368,72 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // Should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testBeepVolume_politeNotif_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + // NOTIFICATION_COOLDOWN_ALL setting is enabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, 1); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + NotificationRecord r2 = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test public void testBeepVolume_politeNotif_applyPerApp() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); |