diff options
| author | 2023-12-13 18:16:28 +0100 | |
|---|---|---|
| committer | 2023-12-29 11:15:02 +0000 | |
| commit | aefcef1190c19570ef667949e5609ecd27a19019 (patch) | |
| tree | 87818dcecf113d05426ba431426685d01e233f1e | |
| parent | 66d709f9858096f1a5d1e31488b077e114f3347e (diff) | |
Add cross-app notification cooldown
Add a new cooldown strategy that applies to all notifications
across apps.
Adjust the original cooldown strategy to apply to all notifications
per app instread of per channel. Except for channels that have
user-set sounds.
Adjust cooldown timers to: t1=60 seconds and t2=5 seconds.
Test: atest NotificationAttentionHelperTest
Bug: 270456865
Change-Id: I1a536c48d4cd9285aa0446d9f630e82667418f9d
3 files changed, 352 insertions, 148 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 1bd098291a2a..eeea17bf39dd 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -68,10 +68,10 @@ public class SystemUiSystemPropertiesFlags { // TODO b/291899544: for released flags, use resource config values /** Value used by polite notif. feature */ public static final Flag NOTIF_COOLDOWN_T1 = devFlag( - "persist.debug.sysui.notification.notif_cooldown_t1", 5000); + "persist.debug.sysui.notification.notif_cooldown_t1", 60000); /** Value used by polite notif. feature */ public static final Flag NOTIF_COOLDOWN_T2 = devFlag( - "persist.debug.sysui.notification.notif_cooldown_t2", 3000); + "persist.debug.sysui.notification.notif_cooldown_t2", 5000); /** Value used by polite notif. feature */ public static final Flag NOTIF_VOLUME1 = devFlag( "persist.debug.sysui.notification.notif_volume1", 30); @@ -80,12 +80,6 @@ public class SystemUiSystemPropertiesFlags { /** Value used by polite notif. feature. -1 to ignore the counter */ public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag( "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10); - /** - * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1, - * rule2 - */ - public static final Flag NOTIF_COOLDOWN_RULE = devFlag( - "persist.debug.sysui.notification.notif_cooldown_rule", "rule1"); /** b/303716154: For debugging only: use short bitmap duration. */ public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag( diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 0117c3ca633b..a6f71c29b380 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -94,8 +94,6 @@ public final class NotificationAttentionHelper { private static final float DEFAULT_VOLUME = 1.0f; // TODO (b/291899544): remove for release - private static final String POLITE_STRATEGY1 = "rule1"; - private static final String POLITE_STRATEGY2 = "rule2"; private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1; private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0; private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1; @@ -146,7 +144,6 @@ public final class NotificationAttentionHelper { private boolean mNotificationCooldownApplyToAll; private boolean mNotificationCooldownVibrateUnlocked; - private boolean mEnablePoliteNotificationsFeature; private final PolitenessStrategy mStrategy; private int mCurrentWorkProfileId = UserHandle.USER_NULL; @@ -192,9 +189,7 @@ public final class NotificationAttentionHelper { .build(); mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume); - mEnablePoliteNotificationsFeature = Flags.politeNotifications(); - - if (mEnablePoliteNotificationsFeature) { + if (Flags.politeNotifications()) { mStrategy = getPolitenessStrategy(); } else { mStrategy = null; @@ -205,21 +200,23 @@ public final class NotificationAttentionHelper { } private PolitenessStrategy getPolitenessStrategy() { - final String politenessStrategy = mFlagResolver.getStringValue( - NotificationFlags.NOTIF_COOLDOWN_RULE); + if (Flags.crossAppPoliteNotifications()) { + PolitenessStrategy appStrategy = 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)); - if (POLITE_STRATEGY2.equals(politenessStrategy)) { - return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + return new StrategyGlobal( + 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_VOLUME2), + appStrategy); } else { - if (!POLITE_STRATEGY1.equals(politenessStrategy)) { - Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to " - + POLITE_STRATEGY1); - } - - return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + 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), @@ -266,7 +263,7 @@ public final class NotificationAttentionHelper { mContext.getContentResolver().registerContentObserver( SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver, UserHandle.USER_ALL); - if (mEnablePoliteNotificationsFeature) { + if (Flags.politeNotifications()) { mContext.getContentResolver().registerContentObserver( SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver, UserHandle.USER_ALL); @@ -280,7 +277,7 @@ public final class NotificationAttentionHelper { } private void loadUserSettings() { - if (mEnablePoliteNotificationsFeature) { + if (Flags.politeNotifications()) { try { mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser()); @@ -301,6 +298,7 @@ public final class NotificationAttentionHelper { mContext.getContentResolver(), Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT) != 0; + mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll); if (Flags.vibrateWhileUnlocked()) { mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser( mContext.getContentResolver(), @@ -484,10 +482,10 @@ public final class NotificationAttentionHelper { getPolitenessState(record)); } record.setAudiblyAlerted(buzz || beep); - if (mEnablePoliteNotificationsFeature) { + if (Flags.politeNotifications()) { // Update last alert time if (buzz || beep) { - record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis()); + mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis()); } } return buzzBeepBlinkLoggingCode; @@ -620,7 +618,7 @@ public final class NotificationAttentionHelper { private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) { // Check feature flag - if (!mEnablePoliteNotificationsFeature) { + if (!Flags.politeNotifications()) { return false; } @@ -1066,9 +1064,13 @@ public final class NotificationAttentionHelper { // Volume for muted state protected final float mVolumeMuted; + protected boolean mApplyPerPackage; + protected final Map<String, Long> mLastUpdatedTimestampByPackage; + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) { mVolumeStates = new HashMap<>(); + mLastUpdatedTimestampByPackage = new HashMap<>(); this.mTimeoutPolite = timeoutPolite; this.mTimeoutMuted = timeoutMuted; @@ -1078,10 +1080,38 @@ public final class NotificationAttentionHelper { abstract void onNotificationPosted(NotificationRecord record); + /** + * Set true if the cooldown strategy should apply per app(package). + * Otherwise apply per conversation channel. + * @param applyPerPackage if the cooldown should be applied per app + */ + void setApplyCooldownPerPackage(boolean applyPerPackage) { + mApplyPerPackage = applyPerPackage; + } + + boolean shouldIgnoreNotification(final NotificationRecord record) { + // Ignore group summaries + return (record.getSbn().isGroup() && record.getSbn().getNotification() + .isGroupSummary()); + } + + /** + * Get the key that determines the grouping for the cooldown behavior. + * + * @param record the notification being posted + * @return the key to group this notification under + */ String getChannelKey(final NotificationRecord record) { - // use conversationId if it's a conversation + // Use conversationId if it's a conversation String channelId = record.getChannel().getConversationId() != null ? record.getChannel().getConversationId() : record.getChannel().getId(); + + // Use only the package name to apply cooldown per app, unless the user explicitly + // changed the channel notification sound => treat separately + if (mApplyPerPackage && !record.getChannel().hasUserSetSound()) { + channelId = ""; + } + return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName() + ":" + channelId; } @@ -1123,12 +1153,59 @@ public final class NotificationAttentionHelper { final String key = getChannelKey(record); // reset to default state after user interaction mVolumeStates.put(key, POLITE_STATE_DEFAULT); - record.getChannel().setLastNotificationUpdateTimeMs(0); + setLastNotificationUpdateTimeMs(record, 0); } public final @PolitenessState int getPolitenessState(final NotificationRecord record) { return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT); } + + void setLastNotificationUpdateTimeMs(final NotificationRecord record, + long timestampMillis) { + record.getChannel().setLastNotificationUpdateTimeMs(timestampMillis); + mLastUpdatedTimestampByPackage.put(record.getSbn().getPackageName(), timestampMillis); + } + + long getLastNotificationUpdateTimeMs(final NotificationRecord record) { + if (record.getChannel().hasUserSetSound() || !mApplyPerPackage) { + return record.getChannel().getLastNotificationUpdateTimeMs(); + } else { + return mLastUpdatedTimestampByPackage.getOrDefault(record.getSbn().getPackageName(), + 0L); + } + } + + @PolitenessState int getNextState(@PolitenessState final int currState, + final long timeSinceLastNotif) { + @PolitenessState int nextState = currState; + switch (currState) { + case POLITE_STATE_DEFAULT: + if (timeSinceLastNotif < mTimeoutPolite) { + nextState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_POLITE: + if (timeSinceLastNotif < mTimeoutMuted) { + nextState = POLITE_STATE_MUTED; + } else if (timeSinceLastNotif > mTimeoutPolite) { + nextState = POLITE_STATE_DEFAULT; + } else { + nextState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_MUTED: + if (timeSinceLastNotif > mTimeoutMuted) { + nextState = POLITE_STATE_POLITE; + } else { + nextState = POLITE_STATE_MUTED; + } + break; + default: + Log.w(TAG, "getNextState unexpected volume state: " + currState); + break; + } + return nextState; + } } // TODO b/270456865: Only one of the two strategies will be released. @@ -1145,72 +1222,51 @@ public final class NotificationAttentionHelper { * after timeoutMuted. * - Transitions back to the default state after a user interaction with a notification. */ - public static class Strategy1 extends PolitenessStrategy { + private static class StrategyPerApp extends PolitenessStrategy { // Keep track of the number of notifications posted per channel private final Map<String, Integer> mNumPosted; // Reset to default state if number of posted notifications exceed this value when muted private final int mMaxPostedForReset; - public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, - int maxPosted) { + public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite, + int volumeMuted, int maxPosted) { super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); mNumPosted = new HashMap<>(); mMaxPostedForReset = maxPosted; if (DEBUG) { - Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted); + Log.i(TAG, "StrategyPerApp: " + timeoutPolite + " " + timeoutMuted); } } @Override public void onNotificationPosted(final NotificationRecord record) { - long timeSinceLastNotif = System.currentTimeMillis() - - record.getChannel().getLastNotificationUpdateTimeMs(); + if (shouldIgnoreNotification(record)) { + return; + } + + long timeSinceLastNotif = + System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record); final String key = getChannelKey(record); - @PolitenessState int volState = getPolitenessState(record); + @PolitenessState final int currState = getPolitenessState(record); + @PolitenessState int 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; mNumPosted.put(key, numPosted); - - switch (volState) { - case POLITE_STATE_DEFAULT: - if (timeSinceLastNotif < mTimeoutPolite) { - volState = POLITE_STATE_POLITE; - } - break; - case POLITE_STATE_POLITE: - if (timeSinceLastNotif < mTimeoutMuted) { - volState = POLITE_STATE_MUTED; - } else if (timeSinceLastNotif > mTimeoutPolite) { - volState = POLITE_STATE_DEFAULT; - } else { - volState = POLITE_STATE_POLITE; - } - break; - case POLITE_STATE_MUTED: - if (timeSinceLastNotif > mTimeoutMuted) { - volState = POLITE_STATE_POLITE; - } else { - volState = POLITE_STATE_MUTED; - } - if (numPosted >= mMaxPostedForReset) { - volState = POLITE_STATE_DEFAULT; - mNumPosted.put(key, 0); - } - break; - default: - Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); - break; + if (currState == POLITE_STATE_MUTED && numPosted >= mMaxPostedForReset) { + nextState = POLITE_STATE_DEFAULT; + mNumPosted.put(key, 0); } if (DEBUG) { Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " - + volState + " key: " + key + " numposted " + numPosted); + + nextState + " key: " + key + " numposted " + numPosted); } - mVolumeStates.put(key, volState); + mVolumeStates.put(key, nextState); } @Override @@ -1221,61 +1277,98 @@ public final class NotificationAttentionHelper { } /** - * Polite notification strategy 2: - * - Transitions from default (loud) => muted state if a notification - * alerts the same channel before timeoutPolite. - * - Transitions from polite => default state if a notification - * alerts the same channel before timeoutMuted. - * - Transitions from muted => default state if a notification alerts after timeoutMuted, - * otherwise transitions to the polite state. - * - Transitions back to the default state after a user interaction with a notification. + * Global (cross-app) strategy. */ - public static class Strategy2 extends PolitenessStrategy { - public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) { + private static class StrategyGlobal extends PolitenessStrategy { + private static final String COMMON_KEY = "cross_app_common_key"; + + private final PolitenessStrategy mAppStrategy; + private long mLastNotificationTimestamp = 0; + + public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite, + int volumeMuted, PolitenessStrategy appStrategy) { super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + mAppStrategy = appStrategy; + if (DEBUG) { - Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted); + Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted); } } @Override - public void onNotificationPosted(final NotificationRecord record) { - long timeSinceLastNotif = System.currentTimeMillis() - - record.getChannel().getLastNotificationUpdateTimeMs(); + void onNotificationPosted(NotificationRecord record) { + if (shouldIgnoreNotification(record)) { + return; + } + + long timeSinceLastNotif = + System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record); final String key = getChannelKey(record); - @PolitenessState int volState = getPolitenessState(record); + @PolitenessState final int currState = getPolitenessState(record); + @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); - switch (volState) { - case POLITE_STATE_DEFAULT: - if (timeSinceLastNotif < mTimeoutPolite) { - volState = POLITE_STATE_MUTED; - } - break; - case POLITE_STATE_POLITE: - if (timeSinceLastNotif > mTimeoutMuted) { - volState = POLITE_STATE_DEFAULT; - } - break; - case POLITE_STATE_MUTED: - if (timeSinceLastNotif > mTimeoutMuted) { - volState = POLITE_STATE_DEFAULT; - } else { - volState = POLITE_STATE_POLITE; - } - break; - default: - Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); - break; + if (DEBUG) { + Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif + + " vol state: " + nextState + " key: " + key); } - if (DEBUG) { - Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " - + volState + " key: " + key); + mVolumeStates.put(key, nextState); + + mAppStrategy.onNotificationPosted(record); + } + + @Override + public float getSoundVolume(final NotificationRecord record) { + final @PolitenessState int globalVolState = getPolitenessState(record); + final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record); + + // Prioritize the most polite outcome + if (globalVolState > appVolState) { + return super.getSoundVolume(record); + } else { + return mAppStrategy.getSoundVolume(record); } + } + + @Override + public void onUserInteraction(final NotificationRecord record) { + super.onUserInteraction(record); + mAppStrategy.onUserInteraction(record); + } - mVolumeStates.put(key, volState); + @Override + String getChannelKey(final NotificationRecord record) { + // If the user explicitly changed the channel notification sound: + // handle as a separate channel + if (record.getChannel().hasUserSetSound()) { + return super.getChannelKey(record); + } else { + // Use one global key per user + return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY; + } + } + + @Override + public void setLastNotificationUpdateTimeMs(NotificationRecord record, + long timestampMillis) { + super.setLastNotificationUpdateTimeMs(record, timestampMillis); + mLastNotificationTimestamp = timestampMillis; + } + + long getLastNotificationUpdateTimeMs(final NotificationRecord record) { + if (record.getChannel().hasUserSetSound()) { + return super.getLastNotificationUpdateTimeMs(record); + } else { + return mLastNotificationTimestamp; + } + } + + @Override + void setApplyCooldownPerPackage(boolean applyPerPackage) { + super.setApplyCooldownPerPackage(applyPerPackage); + mAppStrategy.setApplyCooldownPerPackage(applyPerPackage); } } @@ -1340,7 +1433,7 @@ public final class NotificationAttentionHelper { updateLightsLocked(); } } - if (mEnablePoliteNotificationsFeature) { + if (Flags.politeNotifications()) { if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) { mNotificationCooldownEnabled = Settings.System.getIntForUser( mContext.getContentResolver(), @@ -1365,6 +1458,7 @@ public final class NotificationAttentionHelper { Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT) != 0; + mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll); } if (Flags.vibrateWhileUnlocked()) { if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) { 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 220321d19dac..1b77b99e7d3e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -19,17 +19,21 @@ import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.GROUP_ALERT_ALL; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.GROUP_ALERT_SUMMARY; +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.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; + import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -195,7 +199,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // TODO (b/291907312): remove feature flag mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER); // Disable feature flags by default. Tests should enable as needed. - mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS); + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, + Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED); mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); @@ -364,10 +369,20 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } private NotificationRecord getNotificationRecord(int id, - boolean insistent, boolean once, - boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, - boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, - boolean isLeanback, UserHandle userHandle) { + boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, + boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, + boolean isLeanback, UserHandle userHandle) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration, + defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle, + mPkg); + } + + private NotificationRecord getNotificationRecord(int id, + boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, + boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, + boolean isLeanback, UserHandle userHandle, String packageName) { final Builder builder = new Builder(getContext()) .setContentTitle("foo") @@ -427,8 +442,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) .thenReturn(isLeanback); - StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, - mPid, n, userHandle, null, System.currentTimeMillis()); + StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag, + mUid, mPid, n, userHandle, null, System.currentTimeMillis()); NotificationRecord r = new NotificationRecord(context, sbn, mChannel); mService.addNotification(r); return r; @@ -1990,7 +2005,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { public void testBeepVolume_politeNotif() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); initAttentionHelper(flagResolver); @@ -2015,13 +2029,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } - // TODO b/270456865: Only one of the two strategies will be released. - // The other one need to be removed @Test - public void testBeepVolume_politeNotif_Strategy2() throws Exception { + public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); initAttentionHelper(flagResolver); @@ -2032,14 +2044,58 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); Mockito.reset(mRingtonePlayer); - // update should beep at 0% volume - r.isUpdate = true; - mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + // Use different package for next notifications + NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */, true, true, + false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg"); + + // update should beep at 50% volume + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + // Use different package for next notifications + NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */, true, true, + false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg"); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); verifyBeepVolume(0.0f); + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // Use package with user-set sounds for next notifications + mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT); + mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND); + NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */, true, true, + false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg"); + + // update should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + // 2nd update should beep at 50% volume Mockito.reset(mRingtonePlayer); - mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); verifyBeepVolume(0.5f); verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); @@ -2047,39 +2103,101 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test - public void testVibrationIntensity_politeNotif() throws Exception { + public void testBeepVolume_politeNotif_applyPerApp() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); 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 = getBuzzyBeepyNotification(); + NotificationRecord r = getBeepyNotification(); // set up internal state mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); - VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); - Mockito.reset(vibratorHelper); + // Use different channel for next notifications + mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT); - // update should buzz at 50% intensity - r.isUpdate = true; - mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + // update should beep at 50% volume + NotificationRecord r2 = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); - verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); - Mockito.reset(vibratorHelper); + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); - // 2nd update should buzz at 0% intensity + // Use different package for next notifications + NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */, true, true, + false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg"); + + // Update from new package should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_applyPerApp_ChannelHasUserSound() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + 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(); + + // set up internal state mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); - verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + Mockito.reset(mRingtonePlayer); + + // Use different channel for next notifications + mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT); + mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND); + + // update should beep at 100% volume + NotificationRecord r2 = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 50% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + // Use different package for next notifications + mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT); + NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, false /* buzzy*/, false /* lights */, true, true, + false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg"); + + // Update from new package should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @Test - public void testVibrationIntensity_politeNotif_Strategy2() throws Exception { + public void testVibrationIntensity_politeNotif() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); initAttentionHelper(flagResolver); @@ -2092,16 +2210,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); Mockito.reset(vibratorHelper); - // update should buzz at 0% intensity + // update should buzz at 50% intensity r.isUpdate = true; mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); - verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); Mockito.reset(vibratorHelper); - // 2nd update should buzz at 50% intensity + // 2nd update should buzz at 0% intensity mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); - verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); + verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); } @Test @@ -2162,7 +2280,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { public void testBeepVolume_politeNotif_workProfile() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); @@ -2203,7 +2320,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); TestableFlagResolver flagResolver = new TestableFlagResolver(); - flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); |