1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.notification;
18 
19 import static android.app.Flags.sortSectionByTime;
20 import static android.app.Notification.FLAG_INSISTENT;
21 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
22 import static android.app.NotificationManager.IMPORTANCE_MIN;
23 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
24 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
25 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
26 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
27 import static android.media.audio.Flags.focusExclusiveWithRecording;
28 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
29 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
30 import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
31 
32 import android.Manifest.permission;
33 import android.annotation.IntDef;
34 import android.app.ActivityManager;
35 import android.app.KeyguardManager;
36 import android.app.Notification;
37 import android.app.NotificationManager;
38 import android.app.StatusBarManager;
39 import android.content.BroadcastReceiver;
40 import android.content.ContentResolver;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.pm.PackageManager;
45 import android.content.pm.UserInfo;
46 import android.content.res.Resources;
47 import android.database.ContentObserver;
48 import android.media.AudioAttributes;
49 import android.media.AudioManager;
50 import android.media.IRingtonePlayer;
51 import android.net.Uri;
52 import android.os.Binder;
53 import android.os.RemoteException;
54 import android.os.SystemProperties;
55 import android.os.UserHandle;
56 import android.os.UserManager;
57 import android.os.VibrationEffect;
58 import android.provider.Settings;
59 import android.telephony.PhoneStateListener;
60 import android.telephony.TelephonyManager;
61 import android.text.TextUtils;
62 import android.util.Log;
63 import android.util.Pair;
64 import android.util.Slog;
65 import android.view.accessibility.AccessibilityEvent;
66 import android.view.accessibility.AccessibilityManager;
67 
68 import com.android.internal.R;
69 import com.android.internal.annotations.VisibleForTesting;
70 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
71 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
72 import com.android.internal.logging.MetricsLogger;
73 import com.android.internal.logging.nano.MetricsProto;
74 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
75 import com.android.server.EventLogTags;
76 import com.android.server.lights.LightsManager;
77 import com.android.server.lights.LogicalLight;
78 
79 import java.io.PrintWriter;
80 import java.lang.annotation.Retention;
81 import java.lang.annotation.RetentionPolicy;
82 import java.util.ArrayList;
83 import java.util.HashMap;
84 import java.util.List;
85 import java.util.Map;
86 import java.util.Objects;
87 import java.util.Set;
88 
89 /**
90  * NotificationManagerService helper for handling notification attention effects:
91  *  make noise, vibrate, or flash the LED.
92  * @hide
93  */
94 public final class NotificationAttentionHelper {
95     static final String TAG = "NotifAttentionHelper";
96     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
97     static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
98             "debug.notification.interruptiveness", false);
99 
100     private static final float DEFAULT_VOLUME = 1.0f;
101     // TODO (b/291899544): remove for release
102     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
103     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 1;
104     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
105     private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
106 
107     @VisibleForTesting
108     static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
109             Intent.ACTION_AIRPLANE_MODE_CHANGED,
110             Intent.ACTION_BOOT_COMPLETED,
111             Intent.ACTION_USER_SWITCHED,
112             Intent.ACTION_MANAGED_PROFILE_AVAILABLE
113     );
114 
115     @VisibleForTesting
116     static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
117             Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
118             Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
119     );
120 
121     private final Context mContext;
122     private final PackageManager mPackageManager;
123     private final TelephonyManager mTelephonyManager;
124     private final UserManager mUm;
125     private final NotificationManagerPrivate mNMP;
126     private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver;
127     private AccessibilityManager mAccessibilityManager;
128     private KeyguardManager mKeyguardManager;
129     private AudioManager mAudioManager;
130     private final NotificationUsageStats mUsageStats;
131     private final ZenModeHelper mZenModeHelper;
132 
133     private VibratorHelper mVibratorHelper;
134     // The last key in this list owns the hardware.
135     ArrayList<String> mLights = new ArrayList<>();
136     private LogicalLight mNotificationLight;
137     private LogicalLight mAttentionLight;
138 
139     private final boolean mUseAttentionLight;
140     boolean mHasLight;
141 
142     private final SettingsObserver mSettingsObserver;
143 
144     private boolean mIsAutomotive;
145     private boolean mNotificationEffectsEnabledForAutomotive;
146     private boolean mDisableNotificationEffects;
147     private int mCallState;
148     private String mSoundNotificationKey;
149     private String mVibrateNotificationKey;
150     private boolean mSystemReady;
151     private boolean mInCallStateOffHook = false;
152     private boolean mScreenOn = true;
153     private boolean mUserPresent = false;
154     private boolean mNotificationPulseEnabled;
155     private final Uri mInCallNotificationUri;
156     private final AudioAttributes mInCallNotificationAudioAttributes;
157     private final float mInCallNotificationVolume;
158     private Binder mCallNotificationToken = null;
159 
160     // Settings flags
161     private boolean mNotificationCooldownEnabled;
162     private boolean mNotificationCooldownForWorkEnabled;
163     private boolean mNotificationCooldownApplyToAll;
164     private boolean mNotificationCooldownVibrateUnlocked;
165 
166     private final PolitenessStrategy mStrategy;
167     private int mCurrentWorkProfileId = UserHandle.USER_NULL;
168 
NotificationAttentionHelper(Context context, LightsManager lightsManager, AccessibilityManager accessibilityManager, PackageManager packageManager, UserManager userManager, NotificationUsageStats usageStats, NotificationManagerPrivate notificationManagerPrivate, ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver)169     public NotificationAttentionHelper(Context context, LightsManager lightsManager,
170             AccessibilityManager accessibilityManager, PackageManager packageManager,
171             UserManager userManager, NotificationUsageStats usageStats,
172             NotificationManagerPrivate notificationManagerPrivate,
173             ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) {
174         mContext = context;
175         mPackageManager = packageManager;
176         mTelephonyManager = context.getSystemService(TelephonyManager.class);
177         mAccessibilityManager = accessibilityManager;
178         mUm = userManager;
179         mNMP = notificationManagerPrivate;
180         mUsageStats = usageStats;
181         mZenModeHelper = zenModeHelper;
182         mFlagResolver = flagResolver;
183 
184         mVibratorHelper = new VibratorHelper(context);
185 
186         mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
187         mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
188 
189         Resources resources = context.getResources();
190         mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight);
191         mHasLight =
192                 resources.getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed);
193 
194         // Don't start allowing notifications until the setup wizard has run once.
195         // After that, including subsequent boots, init with notifications turned on.
196         // This works on the first boot because the setup wizard will toggle this
197         // flag at least once and we'll go back to 0 after that.
198         if (Settings.Global.getInt(context.getContentResolver(),
199                 Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
200             mDisableNotificationEffects = true;
201         }
202 
203         mInCallNotificationUri = Uri.parse(
204                 "file://" + resources.getString(R.string.config_inCallNotificationSound));
205         mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
206                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
207                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
208                 .build();
209         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
210 
211         if (Flags.politeNotifications()) {
212             mStrategy = createPolitenessStrategy();
213         } else {
214             mStrategy = null;
215         }
216 
217         mSettingsObserver = new SettingsObserver();
218         loadUserSettings();
219     }
220 
createPolitenessStrategy()221     private PolitenessStrategy createPolitenessStrategy() {
222         if (Flags.crossAppPoliteNotifications()) {
223             PolitenessStrategy appStrategy = new StrategyPerApp(
224                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
225                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
226                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
227                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
228                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
229                     record -> mPackageManager.checkPermission(
230                             permission.RECEIVE_EMERGENCY_BROADCAST,
231                             record.getSbn().getPackageName()) == PERMISSION_GRANTED);
232 
233             return new StrategyAvalanche(
234                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
235                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
236                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
237                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
238                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
239                     appStrategy, appStrategy.mExemptionProvider);
240         } else {
241             return new StrategyPerApp(
242                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
243                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
244                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
245                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
246                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET),
247                     record -> mPackageManager.checkPermission(
248                             permission.RECEIVE_EMERGENCY_BROADCAST,
249                             record.getSbn().getPackageName()) == PERMISSION_GRANTED);
250         }
251     }
252 
253     @VisibleForTesting
getPolitenessStrategy()254     PolitenessStrategy getPolitenessStrategy() {
255         return mStrategy;
256     }
257 
onSystemReady()258     public void onSystemReady() {
259         mSystemReady = true;
260 
261         mIsAutomotive = mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0);
262         mNotificationEffectsEnabledForAutomotive = mContext.getResources().getBoolean(
263                 R.bool.config_enableServerNotificationEffectsForAutomotive);
264 
265         mAudioManager = mContext.getSystemService(AudioManager.class);
266         mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
267 
268         registerBroadcastListeners();
269     }
270 
registerBroadcastListeners()271     private void registerBroadcastListeners() {
272         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
273             mTelephonyManager.listen(new PhoneStateListener() {
274                 @Override
275                 public void onCallStateChanged(int state, String incomingNumber) {
276                     if (mCallState == state) return;
277                     if (DEBUG) Slog.d(TAG, "Call state changed: " + callStateToString(state));
278                     mCallState = state;
279                 }
280             }, PhoneStateListener.LISTEN_CALL_STATE);
281         }
282 
283         IntentFilter filter = new IntentFilter();
284         filter.addAction(Intent.ACTION_SCREEN_ON);
285         filter.addAction(Intent.ACTION_SCREEN_OFF);
286         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
287         filter.addAction(Intent.ACTION_USER_PRESENT);
288         filter.addAction(Intent.ACTION_USER_ADDED);
289         filter.addAction(Intent.ACTION_USER_REMOVED);
290         filter.addAction(Intent.ACTION_USER_SWITCHED);
291         filter.addAction(Intent.ACTION_USER_UNLOCKED);
292         if (Flags.crossAppPoliteNotifications()) {
293             for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
294                 filter.addAction(avalancheIntent);
295             }
296         }
297         mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
298 
299         mContext.getContentResolver().registerContentObserver(
300                 SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
301                 UserHandle.USER_ALL);
302         if (Flags.politeNotifications()) {
303             mContext.getContentResolver().registerContentObserver(
304                     SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
305                     UserHandle.USER_ALL);
306             mContext.getContentResolver().registerContentObserver(
307                     SettingsObserver.NOTIFICATION_COOLDOWN_ALL_URI, false, mSettingsObserver,
308                     UserHandle.USER_ALL);
309             mContext.getContentResolver().registerContentObserver(
310                     SettingsObserver.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI, false,
311                     mSettingsObserver, UserHandle.USER_ALL);
312         }
313     }
314 
loadUserSettings()315     private void loadUserSettings() {
316         boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(),
317                 Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0;
318         if (mNotificationPulseEnabled != pulseEnabled) {
319             mNotificationPulseEnabled = pulseEnabled;
320             updateLightsLocked();
321         }
322 
323         if (Flags.politeNotifications()) {
324             try {
325                 mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
326 
327                 mNotificationCooldownEnabled =
328                     Settings.System.getIntForUser(mContext.getContentResolver(),
329                         Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
330                         DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, UserHandle.USER_CURRENT) != 0;
331                 if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
332                     mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
333                         mContext.getContentResolver(),
334                         Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
335                         DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, mCurrentWorkProfileId)
336                         != 0;
337                 } else {
338                     mNotificationCooldownForWorkEnabled = false;
339                 }
340                 mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
341                     mContext.getContentResolver(),
342                     Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
343                     UserHandle.USER_CURRENT) != 0;
344                 mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
345                 if (Flags.vibrateWhileUnlocked()) {
346                     mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
347                         mContext.getContentResolver(),
348                         Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
349                         DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
350                         UserHandle.USER_CURRENT) != 0;
351                 }
352             } catch (Exception e) {
353                 Log.e(TAG, "Failed to read Settings: " + e);
354             }
355         }
356     }
357 
358     @VisibleForTesting
359     /**
360      * Determine whether this notification should attempt to make noise, vibrate, or flash the LED
361      * @return buzzBeepBlink - bitfield (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) |
362      *  (polite_attenuated ? 8 : 0) | (polite_muted ? 16 : 0)
363      */
buzzBeepBlinkLocked(NotificationRecord record, Signals signals)364     int buzzBeepBlinkLocked(NotificationRecord record, Signals signals) {
365         if (mIsAutomotive && !mNotificationEffectsEnabledForAutomotive) {
366             return 0;
367         }
368         boolean buzz = false;
369         boolean beep = false;
370         boolean blink = false;
371 
372         final String key = record.getKey();
373 
374         if (DEBUG) {
375             Log.d(TAG, "buzzBeepBlinkLocked " + record);
376         }
377 
378         if (isPoliteNotificationFeatureEnabled(record)) {
379             mStrategy.onNotificationPosted(record);
380         }
381 
382         // Should this notification make noise, vibe, or use the LED?
383         final boolean aboveThreshold =
384                 mIsAutomotive
385                         ? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
386                         : record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
387         // Remember if this notification already owns the notification channels.
388         boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
389         boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
390         // These are set inside the conditional if the notification is allowed to make noise.
391         boolean hasValidVibrate = false;
392         boolean hasValidSound = false;
393         boolean sentAccessibilityEvent = false;
394 
395         // If the notification will appear in the status bar, it should send an accessibility event
396         final boolean suppressedByDnd = record.isIntercepted()
397                 && (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
398         if (!record.isUpdate
399                 && record.getImportance() > IMPORTANCE_MIN
400                 && !suppressedByDnd
401                 && isNotificationForCurrentUser(record, signals)) {
402             sendAccessibilityEvent(record);
403             sentAccessibilityEvent = true;
404         }
405 
406         if (aboveThreshold && isNotificationForCurrentUser(record, signals)) {
407             if (mSystemReady && mAudioManager != null) {
408                 Uri soundUri = record.getSound();
409                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
410                 VibrationEffect vibration = record.getVibration();
411                 // Demote sound to vibration if vibration missing & phone in vibration mode.
412                 if (vibration == null
413                         && hasValidSound
414                         && (mAudioManager.getRingerModeInternal()
415                         == AudioManager.RINGER_MODE_VIBRATE)
416                         && mAudioManager.getStreamVolume(
417                         AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
418                     boolean insistent = (record.getFlags() & Notification.FLAG_INSISTENT) != 0;
419                     vibration = mVibratorHelper.createFallbackVibration(insistent);
420                 }
421                 hasValidVibrate = vibration != null;
422                 // Vibration-only if unlocked and Settings flag set
423                 boolean vibrateOnly =
424                         hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
425                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
426                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
427                     if (!sentAccessibilityEvent) {
428                         sendAccessibilityEvent(record);
429                         sentAccessibilityEvent = true;
430                     }
431                     if (DEBUG) Slog.v(TAG, "Interrupting!");
432                     boolean isInsistentUpdate = isInsistentUpdate(record);
433                     if (hasValidSound && !vibrateOnly) {
434                         if (isInsistentUpdate) {
435                             // don't reset insistent sound, it's jarring
436                             beep = true;
437                         } else {
438                             if (isInCall()) {
439                                 playInCallNotification();
440                                 beep = true;
441                             } else {
442                                 beep = playSound(record, soundUri);
443                             }
444                             if (beep) {
445                                 mSoundNotificationKey = key;
446                             }
447                         }
448                     }
449 
450                     final boolean ringerModeSilent =
451                             mAudioManager.getRingerModeInternal()
452                                     == AudioManager.RINGER_MODE_SILENT;
453                     if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
454                         if (isInsistentUpdate) {
455                             buzz = true;
456                         } else {
457                             buzz = playVibration(record, vibration, hasValidSound && !vibrateOnly);
458                             if (buzz) {
459                                 mVibrateNotificationKey = key;
460                             }
461                         }
462                     }
463 
464                     // Try to start flash notification event whenever an audible and non-suppressed
465                     // notification is received
466                     mAccessibilityManager.startFlashNotificationEvent(mContext,
467                             AccessibilityManager.FLASH_REASON_NOTIFICATION,
468                             record.getSbn().getPackageName());
469 
470                 } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
471                     hasValidSound = false;
472                 }
473             }
474         }
475         // If a notification is updated to remove the actively playing sound or vibrate,
476         // cancel that feedback now
477         if (wasBeep && !hasValidSound) {
478             clearSoundLocked();
479         }
480         if (wasBuzz && !hasValidVibrate) {
481             clearVibrateLocked();
482         }
483 
484         // light
485         // release the light
486         boolean wasShowLights = mLights.remove(key);
487         if (canShowLightsLocked(record, signals, aboveThreshold)) {
488             mLights.add(key);
489             updateLightsLocked();
490             if (mUseAttentionLight && mAttentionLight != null) {
491                 mAttentionLight.pulse();
492             }
493             blink = true;
494         } else if (wasShowLights) {
495             updateLightsLocked();
496         }
497         if (buzz || beep || blink) {
498             // Ignore summary updates because we don't display most of the information.
499             if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) {
500                 if (DEBUG_INTERRUPTIVENESS) {
501                     Slog.v(TAG, "INTERRUPTIVENESS: "
502                             + record.getKey() + " is not interruptive: summary");
503                 }
504             } else if (record.canBubble()) {
505                 if (DEBUG_INTERRUPTIVENESS) {
506                     Slog.v(TAG, "INTERRUPTIVENESS: "
507                             + record.getKey() + " is not interruptive: bubble");
508                 }
509             } else {
510                 record.setInterruptive(true);
511                 if (DEBUG_INTERRUPTIVENESS) {
512                     Slog.v(TAG, "INTERRUPTIVENESS: "
513                             + record.getKey() + " is interruptive: alerted");
514                 }
515                 if (sortSectionByTime()) {
516                     if (buzz || beep) {
517                         record.resetRankingTime();
518                     }
519                 }
520             }
521         }
522         final int buzzBeepBlinkLoggingCode =
523                 (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
524         if (buzzBeepBlinkLoggingCode > 0) {
525             MetricsLogger.action(record.getLogMaker()
526                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
527                     .setType(MetricsEvent.TYPE_OPEN)
528                     .setSubtype(buzzBeepBlinkLoggingCode));
529             EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
530                     getPolitenessState(record));
531         }
532         if (Flags.politeNotifications()) {
533             // Update last alert time
534             if (buzz || beep) {
535                 mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
536             }
537 
538             record.setAudiblyAlerted((buzz || beep)
539                     && getPolitenessState(record) != PolitenessStrategy.POLITE_STATE_MUTED);
540         } else {
541             record.setAudiblyAlerted(buzz || beep);
542         }
543         return buzzBeepBlinkLoggingCode;
544     }
545 
getPoliteBit(final NotificationRecord record)546     private int getPoliteBit(final NotificationRecord record) {
547         switch (getPolitenessState(record)) {
548             case PolitenessStrategy.POLITE_STATE_POLITE:
549                 return MetricsProto.MetricsEvent.ALERT_POLITE;
550             case PolitenessStrategy.POLITE_STATE_MUTED:
551                 return MetricsProto.MetricsEvent.ALERT_MUTED;
552             default:
553                 return 0;
554         }
555     }
556 
getPolitenessState(final NotificationRecord record)557     private int getPolitenessState(final NotificationRecord record) {
558         if (!isPoliteNotificationFeatureEnabled(record)) {
559             return PolitenessStrategy.POLITE_STATE_DEFAULT;
560         }
561         return mStrategy.getPolitenessState(record);
562     }
563 
isInsistentUpdate(final NotificationRecord record)564     boolean isInsistentUpdate(final NotificationRecord record) {
565         return (Objects.equals(record.getKey(), mSoundNotificationKey)
566                 || Objects.equals(record.getKey(), mVibrateNotificationKey))
567                 && isCurrentlyInsistent();
568     }
569 
isCurrentlyInsistent()570     boolean isCurrentlyInsistent() {
571         return isLoopingRingtoneNotification(mNMP.getNotificationByKey(mSoundNotificationKey))
572                 || isLoopingRingtoneNotification(
573                 mNMP.getNotificationByKey(mVibrateNotificationKey));
574     }
575 
shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals)576     boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
577         // Suppressed because it's a silent update
578         final Notification notification = record.getNotification();
579         if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
580             return true;
581         }
582 
583         // Suppressed because a user manually unsnoozed something (or similar)
584         if (record.shouldPostSilently()) {
585             return true;
586         }
587 
588         // muted by listener
589         final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
590         if (disableEffects != null) {
591             ZenLog.traceDisableEffects(record, disableEffects);
592             return true;
593         }
594 
595         // suppressed due to DND
596         if (record.isIntercepted()) {
597             return true;
598         }
599 
600         // Suppressed because another notification in its group handles alerting
601         if (record.getSbn().isGroup()) {
602             if (notification.suppressAlertingDueToGrouping()) {
603                 return true;
604             }
605         }
606 
607         // Suppressed for being too recently noisy
608         final String pkg = record.getSbn().getPackageName();
609         if (mUsageStats.isAlertRateLimited(pkg)) {
610             Slog.e(TAG, "Muting recently noisy " + record.getKey());
611             return true;
612         }
613 
614         // A different looping ringtone, such as an incoming call is playing
615         if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
616             return true;
617         }
618 
619         // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
620         final boolean isBubbleOrOverflowed = record.canBubble() && (record.isFlagBubbleRemoved()
621                 || record.getNotification().isBubbleNotification());
622         if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
623                 && record.getNotification().getBubbleMetadata() != null) {
624             if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
625                 return true;
626             }
627         }
628 
629         return false;
630     }
631 
isLoopingRingtoneNotification(final NotificationRecord playingRecord)632     private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
633         if (playingRecord != null) {
634             if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE
635                     && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) {
636                 return true;
637             }
638         }
639         return false;
640     }
641 
playSound(final NotificationRecord record, Uri soundUri)642     private boolean playSound(final NotificationRecord record, Uri soundUri) {
643         final boolean shouldPlay;
644         if (focusExclusiveWithRecording()) {
645             // flagged path
646             shouldPlay = mAudioManager.shouldNotificationSoundPlay(record.getAudioAttributes());
647         } else {
648             // legacy path
649             // play notifications if there is no user of exclusive audio focus
650             // and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
651             //   VIBRATE ringer mode)
652             shouldPlay = !mAudioManager.isAudioFocusExclusive()
653                     && (mAudioManager.getStreamVolume(
654                     AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0);
655         }
656         if (!shouldPlay) {
657             if (DEBUG) Slog.v(TAG, "Not playing sound " + soundUri + " due to focus/volume");
658             return false;
659         }
660 
661         boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
662         final long identity = Binder.clearCallingIdentity();
663         try {
664             final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
665             if (player != null) {
666                 if (DEBUG) {
667                     Slog.v(TAG, "Playing sound " + soundUri + " with attributes "
668                             + record.getAudioAttributes());
669                 }
670                 player.playAsync(soundUri, record.getSbn().getUser(), looping,
671                         record.getAudioAttributes(), getSoundVolume(record));
672                 return true;
673             }
674         } catch (RemoteException e) {
675             Log.e(TAG, "Failed playSound: " + e);
676         } finally {
677             Binder.restoreCallingIdentity(identity);
678         }
679         return false;
680     }
681 
isPoliteNotificationFeatureEnabled(final NotificationRecord record)682     private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
683         // Check feature flag
684         if (!Flags.politeNotifications()) {
685             return false;
686         }
687 
688         // The user can enable/disable notifications cooldown from the Settings app
689         if (!mNotificationCooldownEnabled) {
690             return false;
691         }
692 
693         // The user can enable/disable notifications cooldown for work profile from the Settings app
694         if (isNotificationForWorkProfile(record) && !mNotificationCooldownForWorkEnabled) {
695             return false;
696         }
697 
698         // The user can choose to apply cooldown for all apps/conversations only from the
699         // Settings app
700         if (!mNotificationCooldownApplyToAll && !record.isConversation()) {
701             return false;
702         }
703 
704         return true;
705     }
706 
getSoundVolume(final NotificationRecord record)707     private float getSoundVolume(final NotificationRecord record) {
708         if (!isPoliteNotificationFeatureEnabled(record)) {
709             return DEFAULT_VOLUME;
710         }
711 
712         return mStrategy.getSoundVolume(record);
713     }
714 
getVibrationIntensity(final NotificationRecord record)715     private float getVibrationIntensity(final NotificationRecord record) {
716         if (!isPoliteNotificationFeatureEnabled(record)) {
717             return DEFAULT_VOLUME;
718         }
719 
720         return mStrategy.getVibrationIntensity(record);
721     }
722 
playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound)723     private boolean playVibration(final NotificationRecord record, final VibrationEffect effect,
724             boolean delayVibForSound) {
725         // Escalate privileges so we can use the vibrator even if the
726         // notifying app does not have the VIBRATE permission.
727         final long identity = Binder.clearCallingIdentity();
728         try {
729             final float scale = getVibrationIntensity(record);
730             final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
731                     ? mVibratorHelper.scale(effect, scale) : effect;
732             if (delayVibForSound) {
733                 new Thread(() -> {
734                     // delay the vibration by the same amount as the notification sound
735                     final int waitMs = mAudioManager.getFocusRampTimeMs(
736                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
737                             record.getAudioAttributes());
738                     if (DEBUG) {
739                         Slog.v(TAG, "Delaying vibration for notification "
740                                 + record.getKey() + " by " + waitMs + "ms");
741                     }
742                     try {
743                         Thread.sleep(waitMs);
744                     } catch (InterruptedException e) { }
745                     // Notifications might be canceled before it actually vibrates due to waitMs,
746                     // so need to check that the notification is still valid for vibrate.
747                     if (mNMP.getNotificationByKey(record.getKey()) != null) {
748                         if (record.getKey().equals(mVibrateNotificationKey)) {
749                             vibrate(record, scaledEffect, true);
750                         } else {
751                             if (DEBUG) {
752                                 Slog.v(TAG, "No vibration for notification "
753                                         + record.getKey() + ": a new notification is "
754                                         + "vibrating, or effects were cleared while waiting");
755                             }
756                         }
757                     } else {
758                         Slog.w(TAG, "No vibration for canceled notification "
759                                 + record.getKey());
760                     }
761                 }).start();
762             } else {
763                 vibrate(record, scaledEffect, false);
764             }
765             return true;
766         } finally {
767             Binder.restoreCallingIdentity(identity);
768         }
769     }
770 
vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed)771     private void vibrate(NotificationRecord record, VibrationEffect effect, boolean delayed) {
772         // We need to vibrate as "android" so we can breakthrough DND. VibratorManagerService
773         // doesn't have a concept of vibrating on an app's behalf, so add the app information
774         // to the reason so we can still debug from bugreports
775         String reason = "Notification (" + record.getSbn().getOpPkg() + " "
776                 + record.getSbn().getUid() + ") " + (delayed ? "(Delayed)" : "");
777         mVibratorHelper.vibrate(effect, record.getAudioAttributes(), reason);
778     }
779 
playInCallNotification()780     void playInCallNotification() {
781         // TODO b/270456865: Should we apply politeness to mInCallNotificationVolume ?
782         final ContentResolver cr = mContext.getContentResolver();
783         if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL
784                 && Settings.Secure.getIntForUser(cr,
785                 Settings.Secure.IN_CALL_NOTIFICATION_ENABLED, 1, cr.getUserId()) != 0) {
786             new Thread() {
787                 @Override
788                 public void run() {
789                     final long identity = Binder.clearCallingIdentity();
790                     try {
791                         final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
792                         if (player != null) {
793                             if (mCallNotificationToken != null) {
794                                 player.stop(mCallNotificationToken);
795                             }
796                             mCallNotificationToken = new Binder();
797                             player.play(mCallNotificationToken, mInCallNotificationUri,
798                                     mInCallNotificationAudioAttributes,
799                                     mInCallNotificationVolume, false);
800                         }
801                     } catch (RemoteException e) {
802                         Log.e(TAG, "Failed playInCallNotification: " + e);
803                     } finally {
804                         Binder.restoreCallingIdentity(identity);
805                     }
806                 }
807             }.start();
808         }
809     }
810 
clearSoundLocked()811     void clearSoundLocked() {
812         mSoundNotificationKey = null;
813         final long identity = Binder.clearCallingIdentity();
814         try {
815             final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
816             if (player != null) {
817                 player.stopAsync();
818             }
819         } catch (RemoteException e) {
820             Log.e(TAG, "Failed clearSoundLocked: " + e);
821         } finally {
822             Binder.restoreCallingIdentity(identity);
823         }
824     }
825 
clearVibrateLocked()826     void clearVibrateLocked() {
827         mVibrateNotificationKey = null;
828         final long identity = Binder.clearCallingIdentity();
829         try {
830             mVibratorHelper.cancelVibration();
831         } finally {
832             Binder.restoreCallingIdentity(identity);
833         }
834     }
835 
clearLightsLocked()836     private void clearLightsLocked() {
837         // light
838         mLights.clear();
839         updateLightsLocked();
840     }
841 
clearEffectsLocked(String key)842     public void clearEffectsLocked(String key) {
843         if (key.equals(mSoundNotificationKey)) {
844             clearSoundLocked();
845         }
846         if (key.equals(mVibrateNotificationKey)) {
847             clearVibrateLocked();
848         }
849         boolean removed = mLights.remove(key);
850         if (removed) {
851             updateLightsLocked();
852         }
853     }
854 
clearAttentionEffects()855     public void clearAttentionEffects() {
856         clearSoundLocked();
857         clearVibrateLocked();
858         clearLightsLocked();
859     }
860 
updateLightsLocked()861     void updateLightsLocked() {
862         if (mNotificationLight == null) {
863             return;
864         }
865 
866         // handle notification lights
867         NotificationRecord ledNotification = null;
868         while (ledNotification == null && !mLights.isEmpty()) {
869             final String owner = mLights.get(mLights.size() - 1);
870             ledNotification = mNMP.getNotificationByKey(owner);
871             if (ledNotification == null) {
872                 Slog.wtfStack(TAG, "LED Notification does not exist: " + owner);
873                 mLights.remove(owner);
874             }
875         }
876 
877         // Don't flash while we are in a call or screen is on
878         if (ledNotification == null || isInCall() || mScreenOn) {
879             mNotificationLight.turnOff();
880         } else {
881             NotificationRecord.Light light = ledNotification.getLight();
882             if (light != null && mNotificationPulseEnabled) {
883                 // pulse repeatedly
884                 mNotificationLight.setFlashing(light.color, LogicalLight.LIGHT_FLASH_TIMED,
885                         light.onMs, light.offMs);
886             }
887         }
888     }
889 
canShowLightsLocked(final NotificationRecord record, final Signals signals, boolean aboveThreshold)890     boolean canShowLightsLocked(final NotificationRecord record, final Signals signals,
891             boolean aboveThreshold) {
892         if (!mSystemReady) {
893             return false;
894         }
895         // device lacks light
896         if (!mHasLight) {
897             return false;
898         }
899         // user turned lights off globally
900         if (!mNotificationPulseEnabled) {
901             return false;
902         }
903         // the notification/channel has no light
904         if (record.getLight() == null) {
905             return false;
906         }
907         // unimportant notification
908         if (!aboveThreshold) {
909             return false;
910         }
911         // suppressed due to DND
912         if ((record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_LIGHTS) != 0) {
913             return false;
914         }
915         // Suppressed because it's a silent update
916         final Notification notification = record.getNotification();
917         if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
918             return false;
919         }
920         // Suppressed because another notification in its group handles alerting
921         if (record.getSbn().isGroup() && record.getNotification().suppressAlertingDueToGrouping()) {
922             return false;
923         }
924         // not if in call
925         if (isInCall()) {
926             return false;
927         }
928         // check current user
929         if (!isNotificationForCurrentUser(record, signals)) {
930             return false;
931         }
932         // Light, but only when the screen is off
933         return true;
934     }
935 
disableNotificationEffects(NotificationRecord record, int listenerHints)936     private String disableNotificationEffects(NotificationRecord record, int listenerHints) {
937         if (mDisableNotificationEffects) {
938             return "booleanState";
939         }
940 
941         if ((listenerHints & HINT_HOST_DISABLE_EFFECTS) != 0) {
942             return "listenerHints";
943         }
944         if (record != null && record.getAudioAttributes() != null) {
945             if ((listenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) {
946                 if (record.getAudioAttributes().getUsage()
947                         != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
948                     return "listenerNoti";
949                 }
950             }
951             if ((listenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) {
952                 if (record.getAudioAttributes().getUsage()
953                         == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
954                     return "listenerCall";
955                 }
956             }
957         }
958         if (mCallState != TelephonyManager.CALL_STATE_IDLE && !mZenModeHelper.isCall(record)) {
959             return "callState";
960         }
961 
962         return null;
963     }
964 
updateDisableNotificationEffectsLocked(int status)965     public void updateDisableNotificationEffectsLocked(int status) {
966         mDisableNotificationEffects =
967                 (status & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0;
968         //if (disableNotificationEffects(null) != null) {
969         if (mDisableNotificationEffects) {
970             // cancel whatever is going on
971             clearAttentionEffects();
972         }
973     }
974 
isInCall()975     private boolean isInCall() {
976         if (mInCallStateOffHook) {
977             return true;
978         }
979         int audioMode = mAudioManager.getMode();
980         if (audioMode == AudioManager.MODE_IN_CALL
981                 || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
982             return true;
983         }
984         return false;
985     }
986 
callStateToString(int state)987     private static String callStateToString(int state) {
988         switch (state) {
989             case TelephonyManager.CALL_STATE_IDLE: return "CALL_STATE_IDLE";
990             case TelephonyManager.CALL_STATE_RINGING: return "CALL_STATE_RINGING";
991             case TelephonyManager.CALL_STATE_OFFHOOK: return "CALL_STATE_OFFHOOK";
992             default: return "CALL_STATE_UNKNOWN_" + state;
993         }
994     }
995 
isNotificationForCurrentUser(final NotificationRecord record, final Signals signals)996     private boolean isNotificationForCurrentUser(final NotificationRecord record,
997             final Signals signals) {
998         final int currentUser;
999         final long token = Binder.clearCallingIdentity();
1000         try {
1001             currentUser = ActivityManager.getCurrentUser();
1002         } finally {
1003             Binder.restoreCallingIdentity(token);
1004         }
1005         return (record.getUserId() == UserHandle.USER_ALL || record.getUserId() == currentUser
1006                 || signals.isCurrentProfile);
1007     }
1008 
isNotificationForWorkProfile(final NotificationRecord record)1009     private boolean isNotificationForWorkProfile(final NotificationRecord record) {
1010         return (record.getUser().getIdentifier() == mCurrentWorkProfileId
1011                 && mCurrentWorkProfileId != UserHandle.USER_NULL);
1012     }
1013 
getManagedProfileId(int parentUserId)1014     private int getManagedProfileId(int parentUserId) {
1015         final List<UserInfo> profiles = mUm.getProfiles(parentUserId);
1016         for (UserInfo profile : profiles) {
1017             if (profile.isManagedProfile()
1018                     && profile.getUserHandle().getIdentifier() != parentUserId) {
1019                 return profile.getUserHandle().getIdentifier();
1020             }
1021         }
1022         return UserHandle.USER_NULL;
1023     }
1024 
sendAccessibilityEvent(NotificationRecord record)1025     void sendAccessibilityEvent(NotificationRecord record) {
1026         if (!mAccessibilityManager.isEnabled()) {
1027             return;
1028         }
1029 
1030         final Notification notification = record.getNotification();
1031         final CharSequence packageName = record.getSbn().getPackageName();
1032         final AccessibilityEvent event =
1033                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
1034         event.setPackageName(packageName);
1035         event.setClassName(Notification.class.getName());
1036         final int visibilityOverride = record.getPackageVisibilityOverride();
1037         final int notifVisibility = visibilityOverride == NotificationManager.VISIBILITY_NO_OVERRIDE
1038                 ? notification.visibility : visibilityOverride;
1039         final int userId = record.getUser().getIdentifier();
1040         final boolean needPublic = userId >= 0 && mKeyguardManager.isDeviceLocked(userId);
1041         if (needPublic && notifVisibility != Notification.VISIBILITY_PUBLIC) {
1042             // Emit the public version if we're on the lockscreen and this notification isn't
1043             // publicly visible.
1044             event.setParcelableData(notification.publicVersion);
1045         } else {
1046             event.setParcelableData(notification);
1047         }
1048         final CharSequence tickerText = notification.tickerText;
1049         if (!TextUtils.isEmpty(tickerText)) {
1050             event.getText().add(tickerText);
1051         }
1052 
1053         mAccessibilityManager.sendAccessibilityEvent(event);
1054     }
1055 
1056     /**
1057      * Notify the attention helper of a user interaction with a notification
1058      * @param record that was interacted with
1059      */
onUserInteraction(final NotificationRecord record)1060     public void onUserInteraction(final NotificationRecord record) {
1061         if (isPoliteNotificationFeatureEnabled(record)) {
1062             mStrategy.onUserInteraction(record);
1063         }
1064     }
1065 
dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter)1066     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
1067         pw.println("\n  Notification attention state:");
1068         pw.print(prefix);
1069         pw.println("  mSoundNotificationKey=" + mSoundNotificationKey);
1070         pw.print(prefix);
1071         pw.println("  mVibrateNotificationKey=" + mVibrateNotificationKey);
1072         pw.print(prefix);
1073         pw.println("  mDisableNotificationEffects=" + mDisableNotificationEffects);
1074         pw.print(prefix);
1075         pw.println("  mCallState=" + callStateToString(mCallState));
1076         pw.print(prefix);
1077         pw.println("  mSystemReady=" + mSystemReady);
1078         pw.print(prefix);
1079         pw.println("  mNotificationPulseEnabled=" + mNotificationPulseEnabled);
1080 
1081         int N = mLights.size();
1082         if (N > 0) {
1083             pw.print(prefix);
1084             pw.println("  Lights List:");
1085             for (int i=0; i<N; i++) {
1086                 if (i == N - 1) {
1087                     pw.print("  > ");
1088                 } else {
1089                     pw.print("    ");
1090                 }
1091                 pw.println(mLights.get(i));
1092             }
1093             pw.println("  ");
1094         }
1095 
1096     }
1097 
1098     // External signals set from NMS
1099     public static class Signals {
1100         private final boolean isCurrentProfile;
1101         private final int listenerHints;
1102 
Signals(boolean isCurrentProfile, int listenerHints)1103         public Signals(boolean isCurrentProfile, int listenerHints) {
1104             this.isCurrentProfile = isCurrentProfile;
1105             this.listenerHints = listenerHints;
1106         }
1107     }
1108 
1109     // Returns true if a notification should be exempted from attenuation
1110     private interface ExemptionProvider {
isExempted(NotificationRecord record)1111         boolean isExempted(NotificationRecord record);
1112     }
1113 
1114     @VisibleForTesting
1115     abstract static class PolitenessStrategy {
1116         static final int POLITE_STATE_DEFAULT = 0;
1117         static final int POLITE_STATE_POLITE = 1;
1118         static final int POLITE_STATE_MUTED = 2;
1119 
1120         @IntDef(prefix = { "POLITE_STATE_" }, value = {
1121                 POLITE_STATE_DEFAULT,
1122                 POLITE_STATE_POLITE,
1123                 POLITE_STATE_MUTED,
1124         })
1125         @Retention(RetentionPolicy.SOURCE)
1126         @interface PolitenessState {}
1127 
1128         protected final Map<String, Integer> mVolumeStates;
1129 
1130         // Cooldown timer for transitioning into polite state
1131         protected final int mTimeoutPolite;
1132         // Cooldown timer for transitioning into muted state
1133         protected final int mTimeoutMuted;
1134         // Volume for polite state
1135         protected final float mVolumePolite;
1136         // Volume for muted state
1137         protected final float mVolumeMuted;
1138 
1139         protected boolean mApplyPerPackage;
1140         protected final Map<String, Long> mLastUpdatedTimestampByPackage;
1141 
1142         protected boolean mIsActive = true;
1143 
1144         protected final ExemptionProvider mExemptionProvider;
1145 
PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, ExemptionProvider exemptionProvider)1146         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
1147                 int volumeMuted, ExemptionProvider exemptionProvider) {
1148             mVolumeStates = new HashMap<>();
1149             mLastUpdatedTimestampByPackage = new HashMap<>();
1150 
1151             this.mTimeoutPolite = timeoutPolite;
1152             this.mTimeoutMuted = timeoutMuted;
1153             this.mVolumePolite = volumePolite / 100.0f;
1154             this.mVolumeMuted = volumeMuted / 100.0f;
1155             this.mExemptionProvider = exemptionProvider;
1156         }
1157 
onNotificationPosted(NotificationRecord record)1158         abstract void onNotificationPosted(NotificationRecord record);
1159 
1160         /**
1161          *  Set true if the cooldown strategy should apply per app(package).
1162          *  Otherwise apply per conversation channel.
1163          * @param applyPerPackage if the cooldown should be applied per app
1164          */
setApplyCooldownPerPackage(boolean applyPerPackage)1165         void setApplyCooldownPerPackage(boolean applyPerPackage) {
1166             mApplyPerPackage = applyPerPackage;
1167         }
1168 
shouldIgnoreNotification(final NotificationRecord record)1169         boolean shouldIgnoreNotification(final NotificationRecord record) {
1170             // Ignore group summaries
1171             return (record.getSbn().isGroup() && record.getSbn().getNotification()
1172                     .isGroupSummary());
1173         }
1174 
1175         /**
1176          * Get the key that determines the grouping for the cooldown behavior.
1177          *
1178          * @param record the notification being posted
1179          * @return the key to group this notification under
1180          */
getChannelKey(final NotificationRecord record)1181         String getChannelKey(final NotificationRecord record) {
1182             // Use conversationId if it's a conversation
1183             String channelId = record.getChannel().getConversationId() != null
1184                     ? record.getChannel().getConversationId() : record.getChannel().getId();
1185 
1186             // Use only the package name to apply cooldown per app, unless the user explicitly
1187             // changed the channel notification sound => treat separately
1188             if (mApplyPerPackage && !record.getChannel().hasUserSetSound()) {
1189                 channelId = "";
1190             }
1191 
1192             return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
1193                     + ":" + channelId;
1194         }
1195 
getSoundVolume(final NotificationRecord record)1196         public float getSoundVolume(final NotificationRecord record) {
1197             float volume = DEFAULT_VOLUME;
1198             final String key = getChannelKey(record);
1199             final @PolitenessState int volState = getPolitenessState(record);
1200 
1201             switch (volState) {
1202                 case POLITE_STATE_DEFAULT:
1203                     volume = DEFAULT_VOLUME;
1204                     break;
1205                 case POLITE_STATE_POLITE:
1206                     volume = mVolumePolite;
1207                     break;
1208                 case POLITE_STATE_MUTED:
1209                     volume = mVolumeMuted;
1210                     break;
1211                 default:
1212                     Log.w(TAG, "getSoundVolume unexpected volume state: " + volState);
1213                     break;
1214             }
1215 
1216             if (DEBUG) {
1217                 Log.i(TAG,
1218                         "getSoundVolume state: " + volState + " vol: " + volume + " key: " + key);
1219             }
1220 
1221             return volume;
1222         }
1223 
getVibrationIntensity(final NotificationRecord record)1224         private float getVibrationIntensity(final NotificationRecord record) {
1225             // TODO b/270456865: maybe use different scaling for vibration/sound ?
1226             return getSoundVolume(record);
1227         }
1228 
onUserInteraction(final NotificationRecord record)1229         public void onUserInteraction(final NotificationRecord record) {
1230             final String key = getChannelKey(record);
1231             // reset to default state after user interaction
1232             mVolumeStates.put(key, POLITE_STATE_DEFAULT);
1233             setLastNotificationUpdateTimeMs(record, 0);
1234         }
1235 
getPolitenessState(final NotificationRecord record)1236         public @PolitenessState int getPolitenessState(final NotificationRecord record) {
1237             return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
1238         }
1239 
setLastNotificationUpdateTimeMs(final NotificationRecord record, long timestampMillis)1240         void setLastNotificationUpdateTimeMs(final NotificationRecord record,
1241                 long timestampMillis) {
1242             record.getChannel().setLastNotificationUpdateTimeMs(timestampMillis);
1243             mLastUpdatedTimestampByPackage.put(record.getSbn().getPackageName(), timestampMillis);
1244         }
1245 
getLastNotificationUpdateTimeMs(final NotificationRecord record)1246         long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
1247             if (record.getChannel().hasUserSetSound() || !mApplyPerPackage) {
1248                 return record.getChannel().getLastNotificationUpdateTimeMs();
1249             } else {
1250                 return mLastUpdatedTimestampByPackage.getOrDefault(record.getSbn().getPackageName(),
1251                         0L);
1252             }
1253         }
1254 
getNextState(@olitenessState final int currState, final long timeSinceLastNotif)1255         @PolitenessState int getNextState(@PolitenessState final int currState,
1256                 final long timeSinceLastNotif) {
1257             @PolitenessState int nextState = currState;
1258             switch (currState) {
1259                 case POLITE_STATE_DEFAULT:
1260                     if (timeSinceLastNotif < mTimeoutPolite) {
1261                         nextState = POLITE_STATE_POLITE;
1262                     }
1263                     break;
1264                 case POLITE_STATE_POLITE:
1265                     if (timeSinceLastNotif < mTimeoutMuted) {
1266                         nextState = POLITE_STATE_MUTED;
1267                     } else if (timeSinceLastNotif > mTimeoutPolite) {
1268                         nextState = POLITE_STATE_DEFAULT;
1269                     } else {
1270                         nextState = POLITE_STATE_POLITE;
1271                     }
1272                     break;
1273                 case POLITE_STATE_MUTED:
1274                     if (timeSinceLastNotif > mTimeoutMuted) {
1275                         nextState = POLITE_STATE_POLITE;
1276                     } else {
1277                         nextState = POLITE_STATE_MUTED;
1278                     }
1279                     break;
1280                 default:
1281                     Log.w(TAG, "getNextState unexpected volume state: " + currState);
1282                     break;
1283             }
1284             return nextState;
1285         }
1286 
isActive()1287         boolean isActive() {
1288             return mIsActive;
1289         }
1290     }
1291 
1292     // TODO b/270456865: Only one of the two strategies will be released.
1293     //  The other one need to be removed
1294     /**
1295      *  Polite notification strategy 1:
1296      *   - Transitions from default (loud) => polite (lower volume) state if a notification
1297      *  alerts the same channel before timeoutPolite.
1298      *   - Transitions from polite => muted state if a notification alerts the same channel
1299      *   before timeoutMuted OR transitions back to the default state if a notification alerts
1300      *   after timeoutPolite.
1301      *   - Transitions from muted => default state if the muted channel received more than maxPosted
1302      *  notifications OR transitions back to the polite state if a notification alerts
1303      *  after timeoutMuted.
1304      *  - Transitions back to the default state after a user interaction with a notification.
1305      */
1306     private static class StrategyPerApp extends PolitenessStrategy {
1307         // Keep track of the number of notifications posted per channel
1308         private final Map<String, Integer> mNumPosted;
1309         // Reset to default state if number of posted notifications exceed this value when muted
1310         private final int mMaxPostedForReset;
1311 
StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider)1312         public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
1313                 int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) {
1314             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
1315 
1316             mNumPosted = new HashMap<>();
1317             mMaxPostedForReset = maxPosted;
1318 
1319             if (DEBUG) {
1320                 Log.i(TAG, "StrategyPerApp: " + timeoutPolite + " " + timeoutMuted);
1321             }
1322         }
1323 
1324         @Override
onNotificationPosted(final NotificationRecord record)1325         public void onNotificationPosted(final NotificationRecord record) {
1326             if (shouldIgnoreNotification(record)) {
1327                 return;
1328             }
1329 
1330             long timeSinceLastNotif =
1331                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
1332 
1333             final String key = getChannelKey(record);
1334             @PolitenessState final int currState = getPolitenessState(record);
1335             @PolitenessState int nextState;
1336             if (Flags.politeNotificationsAttnUpdate()) {
1337                 nextState = getNextState(currState, timeSinceLastNotif, record);
1338             } else {
1339                 nextState = getNextState(currState, timeSinceLastNotif);
1340             }
1341 
1342             // Reset to default state if number of posted notifications exceed this value when muted
1343             int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
1344             mNumPosted.put(key, numPosted);
1345             if (currState == POLITE_STATE_MUTED && numPosted >= mMaxPostedForReset) {
1346                 nextState = POLITE_STATE_DEFAULT;
1347                 mNumPosted.put(key, 0);
1348             }
1349 
1350             if (DEBUG) {
1351                 Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
1352                         + nextState + " key: " + key + " numposted " + numPosted);
1353             }
1354 
1355             mVolumeStates.put(key, nextState);
1356         }
1357 
getNextState(@olitenessState final int currState, final long timeSinceLastNotif, final NotificationRecord record)1358         @PolitenessState int getNextState(@PolitenessState final int currState,
1359                 final long timeSinceLastNotif, final NotificationRecord record) {
1360             if (mExemptionProvider.isExempted(record)) {
1361                 return POLITE_STATE_DEFAULT;
1362             }
1363             return getNextState(currState, timeSinceLastNotif);
1364         }
1365 
1366         @Override
onUserInteraction(final NotificationRecord record)1367         public void onUserInteraction(final NotificationRecord record) {
1368             super.onUserInteraction(record);
1369             mNumPosted.put(getChannelKey(record), 0);
1370         }
1371     }
1372 
1373     /**
1374      * Avalanche (cross-app) strategy.
1375      */
1376     private static class StrategyAvalanche extends PolitenessStrategy {
1377         private static final String COMMON_KEY = "cross_app_common_key";
1378 
1379         private final PolitenessStrategy mAppStrategy;
1380         private long mLastNotificationTimestamp = 0;
1381 
1382         private final int mTimeoutAvalanche;
1383         private long mLastAvalancheTriggerTimestamp = 0;
1384 
StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy, ExemptionProvider exemptionProvider)1385         StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
1386                     int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy,
1387                     ExemptionProvider exemptionProvider) {
1388             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider);
1389 
1390             mTimeoutAvalanche = timeoutAvalanche;
1391             mAppStrategy = appStrategy;
1392 
1393             if (DEBUG) {
1394                 Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
1395                         + timeoutAvalanche);
1396             }
1397         }
1398 
1399         @Override
onNotificationPosted(NotificationRecord record)1400         void onNotificationPosted(NotificationRecord record) {
1401             if (isAvalancheActive()) {
1402                 if (shouldIgnoreNotification(record)) {
1403                     return;
1404                 }
1405 
1406                 long timeSinceLastNotif =
1407                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
1408 
1409                 final String key = getChannelKey(record);
1410                 @PolitenessState final int currState = getPolitenessState(record);
1411                 @PolitenessState int nextState;
1412                 if (Flags.politeNotificationsAttnUpdate()) {
1413                     nextState = getNextState(currState, timeSinceLastNotif, record);
1414                 } else {
1415                     nextState = getNextState(currState, timeSinceLastNotif);
1416                 }
1417 
1418                 if (DEBUG) {
1419                     Log.i(TAG,
1420                             "StrategyAvalanche onNotificationPosted time delta: "
1421                             + timeSinceLastNotif
1422                             + " vol state: " + nextState + " key: " + key);
1423                 }
1424 
1425                 mVolumeStates.put(key, nextState);
1426             }
1427 
1428             mAppStrategy.onNotificationPosted(record);
1429         }
1430 
getNextState(@olitenessState final int currState, final long timeSinceLastNotif, final NotificationRecord record)1431         @PolitenessState int getNextState(@PolitenessState final int currState,
1432                 final long timeSinceLastNotif, final NotificationRecord record) {
1433             // Mute all except priority conversations
1434             if (!isAvalancheExempted(record)) {
1435                 return POLITE_STATE_MUTED;
1436             }
1437             if (isAvalancheExemptedFullVolume(record)) {
1438                 return POLITE_STATE_DEFAULT;
1439             }
1440             return getNextState(currState, timeSinceLastNotif);
1441         }
1442 
getPolitenessState(final NotificationRecord record)1443         public @PolitenessState int getPolitenessState(final NotificationRecord record) {
1444             if (isAvalancheActive()) {
1445                 return super.getPolitenessState(record);
1446             } else {
1447                 return mAppStrategy.getPolitenessState(record);
1448             }
1449         }
1450 
1451         @Override
getSoundVolume(final NotificationRecord record)1452         public float getSoundVolume(final NotificationRecord record) {
1453             if (isAvalancheActive()) {
1454                 return super.getSoundVolume(record);
1455             } else {
1456                 return mAppStrategy.getSoundVolume(record);
1457             }
1458         }
1459 
1460         @Override
onUserInteraction(final NotificationRecord record)1461         public void onUserInteraction(final NotificationRecord record) {
1462             super.onUserInteraction(record);
1463             mAppStrategy.onUserInteraction(record);
1464         }
1465 
1466         @Override
getChannelKey(final NotificationRecord record)1467         String getChannelKey(final NotificationRecord record) {
1468             if (isAvalancheActive()) {
1469                 if (Flags.politeNotificationsAttnUpdate()) {
1470                     // Treat high importance conversations independently
1471                     if (isAvalancheExempted(record)) {
1472                         return super.getChannelKey(record);
1473                     } else {
1474                         // Use one global key per user
1475                         return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
1476                     }
1477                 } else {
1478                     // If the user explicitly changed the channel notification sound:
1479                     // handle as a separate channel
1480                     if (record.getChannel().hasUserSetSound()) {
1481                         return super.getChannelKey(record);
1482                     } else {
1483                         // Use one global key per user
1484                         return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
1485                     }
1486                 }
1487             } else {
1488                 return mAppStrategy.getChannelKey(record);
1489             }
1490         }
1491 
1492         @Override
setLastNotificationUpdateTimeMs(NotificationRecord record, long timestampMillis)1493         public void setLastNotificationUpdateTimeMs(NotificationRecord record,
1494                 long timestampMillis) {
1495             super.setLastNotificationUpdateTimeMs(record, timestampMillis);
1496             mLastNotificationTimestamp = timestampMillis;
1497             mAppStrategy.setLastNotificationUpdateTimeMs(record, timestampMillis);
1498         }
1499 
getLastNotificationUpdateTimeMs(final NotificationRecord record)1500         long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
1501             if (Flags.politeNotificationsAttnUpdate()) {
1502                 // Mute all except priority conversations
1503                 if (isAvalancheExempted(record)) {
1504                     return super.getLastNotificationUpdateTimeMs(record);
1505                 } else {
1506                     return mLastNotificationTimestamp;
1507                 }
1508             } else {
1509                 if (record.getChannel().hasUserSetSound()) {
1510                     return super.getLastNotificationUpdateTimeMs(record);
1511                 } else {
1512                     return mLastNotificationTimestamp;
1513                 }
1514             }
1515         }
1516 
1517         @Override
setApplyCooldownPerPackage(boolean applyPerPackage)1518         void setApplyCooldownPerPackage(boolean applyPerPackage) {
1519             super.setApplyCooldownPerPackage(applyPerPackage);
1520             mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
1521         }
1522 
isAvalancheActive()1523         boolean isAvalancheActive() {
1524             mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
1525                     < mTimeoutAvalanche);
1526             if (DEBUG) {
1527                 Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
1528             }
1529             return mIsActive;
1530         }
1531 
1532         @Override
isActive()1533         boolean isActive() {
1534             return isAvalancheActive();
1535         }
1536 
setTriggerTimeMs(long timestamp)1537         void setTriggerTimeMs(long timestamp) {
1538             mLastAvalancheTriggerTimestamp = timestamp;
1539         }
1540 
isAvalancheExemptedFullVolume(final NotificationRecord record)1541         private boolean isAvalancheExemptedFullVolume(final NotificationRecord record) {
1542             // important conversation
1543             if (record.isConversation() && record.getChannel().isImportantConversation()) {
1544                 return true;
1545             }
1546 
1547             // call notification
1548             if (record.getNotification().isStyle(Notification.CallStyle.class)) {
1549                 return true;
1550             }
1551 
1552             // alarm/reminder
1553             final String category = record.getNotification().category;
1554             if (Notification.CATEGORY_REMINDER.equals(category)
1555                     || Notification.CATEGORY_EVENT.equals(category)) {
1556                 return true;
1557             }
1558 
1559             return mExemptionProvider.isExempted(record);
1560         }
1561 
isAvalancheExempted(final NotificationRecord record)1562         private boolean isAvalancheExempted(final NotificationRecord record) {
1563             if (isAvalancheExemptedFullVolume(record)) {
1564                 return true;
1565             }
1566 
1567             // recent conversation
1568             if (record.isConversation()
1569                     && record.getNotification().getWhen() > mLastAvalancheTriggerTimestamp) {
1570                 return true;
1571             }
1572 
1573             if (record.getNotification().fullScreenIntent != null) {
1574                 return true;
1575             }
1576 
1577             if (record.getNotification().isColorized()) {
1578                 return true;
1579             }
1580 
1581             return false;
1582         }
1583     }
1584 
1585     //======================  Observers  =============================
1586     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
1587         @Override
1588         public void onReceive(Context context, Intent intent) {
1589             String action = intent.getAction();
1590 
1591             if (action.equals(Intent.ACTION_SCREEN_ON)) {
1592                 // Keep track of screen on/off state, but do not turn off the notification light
1593                 // until user passes through the lock screen or views the notification.
1594                 mScreenOn = true;
1595                 updateLightsLocked();
1596             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
1597                 mScreenOn = false;
1598                 mUserPresent = false;
1599                 updateLightsLocked();
1600             } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
1601                 mInCallStateOffHook = TelephonyManager.EXTRA_STATE_OFFHOOK
1602                         .equals(intent.getStringExtra(TelephonyManager.EXTRA_STATE));
1603                 updateLightsLocked();
1604             } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
1605                 mUserPresent = true;
1606                 // turn off LED when user passes through lock screen
1607                 if (mNotificationLight != null) {
1608                     mNotificationLight.turnOff();
1609                 }
1610             } else if (action.equals(Intent.ACTION_USER_ADDED)
1611                         || action.equals(Intent.ACTION_USER_REMOVED)
1612                         || action.equals(Intent.ACTION_USER_SWITCHED)
1613                         || action.equals(Intent.ACTION_USER_UNLOCKED)) {
1614                 loadUserSettings();
1615             }
1616 
1617             if (Flags.crossAppPoliteNotifications()) {
1618                 if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
1619                     boolean enableAvalancheStrategy = true;
1620                     // Some actions must also match extras, ie. airplane mode => disabled
1621                     Pair<String, Boolean> expectedExtras =
1622                             NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
1623                     if (expectedExtras != null) {
1624                         enableAvalancheStrategy =
1625                                 intent.getBooleanExtra(expectedExtras.first, false)
1626                                 == expectedExtras.second;
1627                     }
1628 
1629                     if (DEBUG) {
1630                         Log.i(TAG, "Avalanche trigger intent received: " + action
1631                                 + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
1632                     }
1633 
1634                     if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
1635                         ((StrategyAvalanche) mStrategy)
1636                                 .setTriggerTimeMs(System.currentTimeMillis());
1637                     }
1638                 }
1639             }
1640         }
1641     };
1642 
1643     private final class SettingsObserver extends ContentObserver {
1644 
1645         private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor(
1646                 Settings.System.NOTIFICATION_LIGHT_PULSE);
1647         private static final Uri NOTIFICATION_COOLDOWN_ENABLED_URI = Settings.System.getUriFor(
1648                 Settings.System.NOTIFICATION_COOLDOWN_ENABLED);
1649         private static final Uri NOTIFICATION_COOLDOWN_ALL_URI = Settings.System.getUriFor(
1650                 Settings.System.NOTIFICATION_COOLDOWN_ALL);
1651         private static final Uri NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI =
1652                 Settings.System.getUriFor(Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED);
SettingsObserver()1653         public SettingsObserver() {
1654             super(null);
1655         }
1656 
1657         @Override
onChange(boolean selfChange, Uri uri)1658         public void onChange(boolean selfChange, Uri uri) {
1659             if (NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
1660                 boolean pulseEnabled = Settings.System.getIntForUser(
1661                         mContext.getContentResolver(),
1662                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0,
1663                         UserHandle.USER_CURRENT)
1664                         != 0;
1665                 if (mNotificationPulseEnabled != pulseEnabled) {
1666                     mNotificationPulseEnabled = pulseEnabled;
1667                     updateLightsLocked();
1668                 }
1669             }
1670             if (Flags.politeNotifications()) {
1671                 if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
1672                     mNotificationCooldownEnabled = Settings.System.getIntForUser(
1673                             mContext.getContentResolver(),
1674                             Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
1675                             DEFAULT_NOTIFICATION_COOLDOWN_ENABLED,
1676                             UserHandle.USER_CURRENT) != 0;
1677 
1678                     if (mCurrentWorkProfileId != UserHandle.USER_NULL) {
1679                         mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser(
1680                                 mContext.getContentResolver(),
1681                                 Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
1682                                 DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK,
1683                                 mCurrentWorkProfileId)
1684                                 != 0;
1685                     } else {
1686                         mNotificationCooldownForWorkEnabled = false;
1687                     }
1688                 }
1689                 if (NOTIFICATION_COOLDOWN_ALL_URI.equals(uri)) {
1690                     mNotificationCooldownApplyToAll = Settings.System.getIntForUser(
1691                             mContext.getContentResolver(),
1692                             Settings.System.NOTIFICATION_COOLDOWN_ALL,
1693                             DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
1694                             != 0;
1695                     mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
1696                 }
1697                 if (Flags.vibrateWhileUnlocked()) {
1698                     if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
1699                         mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
1700                             mContext.getContentResolver(),
1701                             Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
1702                             DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
1703                             UserHandle.USER_CURRENT) != 0;
1704                     }
1705                 }
1706             }
1707         }
1708     }
1709 
1710 
1711     // TODO b/270456865: cleanup most (all?) of these
1712     //======================= FOR TESTS =====================
1713     @VisibleForTesting
setIsAutomotive(boolean isAutomotive)1714     void setIsAutomotive(boolean isAutomotive) {
1715         mIsAutomotive = isAutomotive;
1716     }
1717 
1718     @VisibleForTesting
setNotificationEffectsEnabledForAutomotive(boolean isEnabled)1719     void setNotificationEffectsEnabledForAutomotive(boolean isEnabled) {
1720         mNotificationEffectsEnabledForAutomotive = isEnabled;
1721     }
1722 
1723     @VisibleForTesting
setSystemReady(boolean systemReady)1724     void setSystemReady(boolean systemReady) {
1725         mSystemReady = systemReady;
1726     }
1727 
1728     @VisibleForTesting
setKeyguardManager(KeyguardManager keyguardManager)1729     void setKeyguardManager(KeyguardManager keyguardManager) {
1730         mKeyguardManager = keyguardManager;
1731     }
1732 
1733     @VisibleForTesting
setAccessibilityManager(AccessibilityManager am)1734     void setAccessibilityManager(AccessibilityManager am) {
1735         mAccessibilityManager = am;
1736     }
1737 
1738     @VisibleForTesting
getVibratorHelper()1739     VibratorHelper getVibratorHelper() {
1740         return mVibratorHelper;
1741     }
1742 
1743     @VisibleForTesting
setVibratorHelper(VibratorHelper helper)1744     void setVibratorHelper(VibratorHelper helper) {
1745         mVibratorHelper = helper;
1746     }
1747 
1748     @VisibleForTesting
setScreenOn(boolean on)1749     void setScreenOn(boolean on) {
1750         mScreenOn = on;
1751     }
1752 
1753     @VisibleForTesting
setUserPresent(boolean userPresent)1754     void setUserPresent(boolean userPresent) {
1755         mUserPresent = userPresent;
1756     }
1757 
1758     @VisibleForTesting
setLights(LogicalLight light)1759     void setLights(LogicalLight light) {
1760         mNotificationLight = light;
1761         mAttentionLight = light;
1762     }
1763 
1764     @VisibleForTesting
setAudioManager(AudioManager audioManager)1765     void setAudioManager(AudioManager audioManager) {
1766         mAudioManager = audioManager;
1767     }
1768 
1769     @VisibleForTesting
setInCallStateOffHook(boolean inCallStateOffHook)1770     void setInCallStateOffHook(boolean inCallStateOffHook) {
1771         mInCallStateOffHook = inCallStateOffHook;
1772     }
1773 
1774 }
1775