diff options
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); |