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