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