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