1 /* 2 * Copyright (C) 2014 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 package com.android.server.notification; 17 18 import static android.app.Flags.restrictAudioAttributesAlarm; 19 import static android.app.Flags.restrictAudioAttributesCall; 20 import static android.app.Flags.restrictAudioAttributesMedia; 21 import static android.app.Flags.sortSectionByTime; 22 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; 23 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 24 import static android.app.NotificationManager.IMPORTANCE_HIGH; 25 import static android.app.NotificationManager.IMPORTANCE_LOW; 26 import static android.app.NotificationManager.IMPORTANCE_MIN; 27 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 28 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; 29 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; 30 31 import android.annotation.FlaggedApi; 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.app.Flags; 35 import android.app.KeyguardManager; 36 import android.app.Notification; 37 import android.app.NotificationChannel; 38 import android.app.Person; 39 import android.content.ContentProvider; 40 import android.content.ContentResolver; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManagerInternal; 45 import android.content.pm.ShortcutInfo; 46 import android.graphics.Bitmap; 47 import android.media.AudioAttributes; 48 import android.media.AudioSystem; 49 import android.metrics.LogMaker; 50 import android.net.Uri; 51 import android.os.Binder; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.IBinder; 55 import android.os.PowerManager; 56 import android.os.Trace; 57 import android.os.UserHandle; 58 import android.os.VibrationEffect; 59 import android.provider.Settings; 60 import android.service.notification.Adjustment; 61 import android.service.notification.NotificationListenerService; 62 import android.service.notification.NotificationRecordProto; 63 import android.service.notification.NotificationStats; 64 import android.service.notification.SnoozeCriterion; 65 import android.service.notification.StatusBarNotification; 66 import android.text.TextUtils; 67 import android.util.ArraySet; 68 import android.util.Log; 69 import android.util.proto.ProtoOutputStream; 70 import android.widget.RemoteViews; 71 72 import com.android.internal.annotations.VisibleForTesting; 73 import com.android.internal.logging.MetricsLogger; 74 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 75 import com.android.server.EventLogTags; 76 import com.android.server.LocalServices; 77 import com.android.server.uri.UriGrantsManagerInternal; 78 79 import dalvik.annotation.optimization.NeverCompile; 80 81 import java.io.PrintWriter; 82 import java.lang.reflect.Array; 83 import java.time.Duration; 84 import java.util.ArrayList; 85 import java.util.Arrays; 86 import java.util.List; 87 import java.util.Objects; 88 89 /** 90 * Holds data about notifications that should not be shared with the 91 * {@link android.service.notification.NotificationListenerService}s. 92 * 93 * <p>These objects should not be mutated unless the code is synchronized 94 * on {@link NotificationManagerService#mNotificationLock}, and any 95 * modification should be followed by a sorting of that list.</p> 96 * 97 * <p>Is sortable by {@link NotificationComparator}.</p> 98 * 99 * {@hide} 100 */ 101 public final class NotificationRecord { 102 static final String TAG = "NotificationRecord"; 103 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 104 // the period after which a notification is updated where it can make sound 105 private static final int MAX_SOUND_DELAY_MS = 2000; 106 private final StatusBarNotification sbn; 107 private final UriGrantsManagerInternal mUgmInternal; 108 final int mTargetSdkVersion; 109 final int mOriginalFlags; 110 private final Context mContext; 111 private KeyguardManager mKeyguardManager; 112 private final PowerManager mPowerManager; 113 NotificationUsageStats.SingleNotificationStats stats; 114 boolean isCanceled; 115 IBinder permissionOwner; 116 117 // These members are used by NotificationSignalExtractors 118 // to communicate with the ranking module. 119 private float mContactAffinity; 120 private boolean mRecentlyIntrusive; 121 private long mLastIntrusive; 122 123 // is this notification currently being intercepted by Zen Mode? 124 private boolean mIntercept; 125 // has the intercept value been set explicitly? we only want to log it if new or changed 126 private boolean mInterceptSet; 127 128 // is this notification hidden since the app pkg is suspended? 129 private boolean mHidden; 130 131 // The timestamp used for ranking. 132 private long mRankingTimeMs; 133 134 // The first post time, stable across updates. 135 private long mCreationTimeMs; 136 137 // The most recent visibility event. 138 private long mVisibleSinceMs; 139 140 // The most recent update time, or the creation time if no updates. 141 @VisibleForTesting 142 final long mUpdateTimeMs; 143 144 // The most recent interruption time, or the creation time if no updates. Differs from the 145 // above value because updates are filtered based on whether they actually interrupted the 146 // user 147 private long mInterruptionTimeMs; 148 149 // The most recent time the notification made noise or buzzed the device, or -1 if it did not. 150 private long mLastAudiblyAlertedMs; 151 152 // Is this record an update of an old record? 153 public boolean isUpdate; 154 private int mPackagePriority; 155 156 private int mAuthoritativeRank; 157 private String mGlobalSortKey; 158 private int mPackageVisibility; 159 private int mSystemImportance = IMPORTANCE_UNSPECIFIED; 160 private int mAssistantImportance = IMPORTANCE_UNSPECIFIED; 161 private int mImportance = IMPORTANCE_UNSPECIFIED; 162 private float mRankingScore = 0f; 163 // Field used in global sort key to bypass normal notifications 164 private int mCriticality = CriticalNotificationExtractor.NORMAL; 165 // A MetricsEvent.NotificationImportanceExplanation, tracking source of mImportance. 166 private int mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN; 167 // A MetricsEvent.NotificationImportanceExplanation for initial importance. 168 private int mInitialImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN; 169 170 private int mSuppressedVisualEffects = 0; 171 private String mUserExplanation; 172 private boolean mPreChannelsNotification = true; 173 private Uri mSound; 174 private VibrationEffect mVibration; 175 private @NonNull AudioAttributes mAttributes; 176 private NotificationChannel mChannel; 177 private ArrayList<String> mPeopleOverride; 178 private ArrayList<SnoozeCriterion> mSnoozeCriteria; 179 private boolean mShowBadge; 180 private boolean mAllowBubble; 181 private Light mLight; 182 private boolean mIsNotConversationOverride; 183 private ShortcutInfo mShortcutInfo; 184 /** 185 * This list contains system generated smart actions from NAS, app-generated smart actions are 186 * stored in Notification.actions with isContextual() set to true. 187 */ 188 private ArrayList<Notification.Action> mSystemGeneratedSmartActions; 189 private ArrayList<CharSequence> mSmartReplies; 190 191 private final List<Adjustment> mAdjustments; 192 private String mAdjustmentIssuer; 193 private final NotificationStats mStats; 194 private int mUserSentiment; 195 private boolean mIsInterruptive; 196 private boolean mTextChanged; 197 private boolean mRecordedInterruption; 198 private int mNumberOfSmartRepliesAdded; 199 private int mNumberOfSmartActionsAdded; 200 private boolean mSuggestionsGeneratedByAssistant; 201 private boolean mEditChoicesBeforeSending; 202 private boolean mHasSeenSmartReplies; 203 private boolean mFlagBubbleRemoved; 204 private boolean mPostSilently; 205 private boolean mHasSentValidMsg; 206 private boolean mAppDemotedFromConvo; 207 private boolean mPkgAllowedAsConvo; 208 private boolean mImportanceFixed; 209 /** 210 * Whether this notification (and its channels) should be considered user locked. Used in 211 * conjunction with user sentiment calculation. 212 */ 213 private boolean mIsAppImportanceLocked; 214 private ArraySet<Uri> mGrantableUris; 215 216 // Storage for phone numbers that were found to be associated with 217 // contacts in this notification. 218 private ArraySet<String> mPhoneNumbers; 219 220 // Whether this notification record should have an update logged the next time notifications 221 // are sorted. 222 private boolean mPendingLogUpdate = false; 223 private int mProposedImportance = IMPORTANCE_UNSPECIFIED; 224 private boolean mSensitiveContent = false; 225 NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel)226 public NotificationRecord(Context context, StatusBarNotification sbn, 227 NotificationChannel channel) { 228 this.sbn = sbn; 229 mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class) 230 .getPackageTargetSdkVersion(sbn.getPackageName()); 231 mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); 232 mOriginalFlags = sbn.getNotification().flags; 233 mRankingTimeMs = calculateRankingTimeMs(0L); 234 mCreationTimeMs = sbn.getPostTime(); 235 mUpdateTimeMs = mCreationTimeMs; 236 mInterruptionTimeMs = mCreationTimeMs; 237 mContext = context; 238 mKeyguardManager = mContext.getSystemService(KeyguardManager.class); 239 mPowerManager = mContext.getSystemService(PowerManager.class); 240 stats = new NotificationUsageStats.SingleNotificationStats(); 241 mChannel = channel; 242 mPreChannelsNotification = isPreChannelsNotification(); 243 mSound = calculateSound(); 244 mVibration = calculateVibration(); 245 mAttributes = calculateAttributes(); 246 mImportance = calculateInitialImportance(); 247 mLight = calculateLights(); 248 mAdjustments = new ArrayList<>(); 249 mStats = new NotificationStats(); 250 calculateUserSentiment(); 251 calculateGrantableUris(); 252 } 253 isPreChannelsNotification()254 private boolean isPreChannelsNotification() { 255 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) { 256 if (mTargetSdkVersion < Build.VERSION_CODES.O) { 257 return true; 258 } 259 } 260 return false; 261 } 262 calculateSound()263 private Uri calculateSound() { 264 final Notification n = getSbn().getNotification(); 265 266 // No notification sounds on tv 267 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 268 return null; 269 } 270 271 Uri sound = mChannel.getSound(); 272 if (mPreChannelsNotification && (getChannel().getUserLockedFields() 273 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 274 275 final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0; 276 if (useDefaultSound) { 277 sound = Settings.System.DEFAULT_NOTIFICATION_URI; 278 } else { 279 sound = n.sound; 280 } 281 } 282 return sound; 283 } 284 calculateLights()285 private Light calculateLights() { 286 int defaultLightColor = mContext.getResources().getColor( 287 com.android.internal.R.color.config_defaultNotificationColor); 288 int defaultLightOn = mContext.getResources().getInteger( 289 com.android.internal.R.integer.config_defaultNotificationLedOn); 290 int defaultLightOff = mContext.getResources().getInteger( 291 com.android.internal.R.integer.config_defaultNotificationLedOff); 292 293 int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor() 294 : defaultLightColor; 295 Light light = getChannel().shouldShowLights() ? new Light(channelLightColor, 296 defaultLightOn, defaultLightOff) : null; 297 if (mPreChannelsNotification 298 && (getChannel().getUserLockedFields() 299 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { 300 final Notification notification = getSbn().getNotification(); 301 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { 302 light = new Light(notification.ledARGB, notification.ledOnMS, 303 notification.ledOffMS); 304 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) { 305 light = new Light(defaultLightColor, defaultLightOn, 306 defaultLightOff); 307 } 308 } else { 309 light = null; 310 } 311 } 312 return light; 313 } 314 getVibrationForChannel( NotificationChannel channel, VibratorHelper helper, boolean insistent)315 private VibrationEffect getVibrationForChannel( 316 NotificationChannel channel, VibratorHelper helper, boolean insistent) { 317 if (!channel.shouldVibrate()) { 318 return null; 319 } 320 321 if (Flags.notificationChannelVibrationEffectApi()) { 322 final VibrationEffect vibration = channel.getVibrationEffect(); 323 if (vibration != null && helper.areEffectComponentsSupported(vibration)) { 324 // Adjust the vibration's repeat behavior based on the `insistent` property. 325 return vibration.applyRepeatingIndefinitely(insistent, /* loopDelayMs= */ 0); 326 } 327 } 328 329 final long[] vibrationPattern = channel.getVibrationPattern(); 330 if (vibrationPattern == null) { 331 return helper.createDefaultVibration(insistent); 332 } 333 return helper.createWaveformVibration(vibrationPattern, insistent); 334 } 335 calculateVibration()336 private VibrationEffect calculateVibration() { 337 VibratorHelper helper = new VibratorHelper(mContext); 338 final Notification notification = getSbn().getNotification(); 339 final boolean insistent = (notification.flags & Notification.FLAG_INSISTENT) != 0; 340 341 if (mPreChannelsNotification 342 && (getChannel().getUserLockedFields() 343 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { 344 final boolean useDefaultVibrate = 345 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 346 if (useDefaultVibrate) { 347 return helper.createDefaultVibration(insistent); 348 } 349 return helper.createWaveformVibration(notification.vibrate, insistent); 350 } 351 return getVibrationForChannel(getChannel(), helper, insistent); 352 } 353 calculateAttributes()354 private @NonNull AudioAttributes calculateAttributes() { 355 final Notification n = getSbn().getNotification(); 356 AudioAttributes attributes = getChannel().getAudioAttributes(); 357 if (attributes == null) { 358 attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; 359 } 360 361 if (mPreChannelsNotification 362 && (getChannel().getUserLockedFields() 363 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 364 if (n.audioAttributes != null) { 365 // prefer audio attributes to stream type 366 attributes = n.audioAttributes; 367 } else if (n.audioStreamType >= 0 368 && n.audioStreamType < AudioSystem.getNumStreamTypes()) { 369 // the stream type is valid, use it 370 attributes = new AudioAttributes.Builder() 371 .setInternalLegacyStreamType(n.audioStreamType) 372 .build(); 373 } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) { 374 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType)); 375 } 376 } 377 return attributes; 378 } 379 calculateInitialImportance()380 private int calculateInitialImportance() { 381 final Notification n = getSbn().getNotification(); 382 int importance = getChannel().getImportance(); // Post-channels notifications use this 383 mInitialImportanceExplanationCode = getChannel().hasUserSetImportance() 384 ? MetricsEvent.IMPORTANCE_EXPLANATION_USER 385 : MetricsEvent.IMPORTANCE_EXPLANATION_APP; 386 387 // Migrate notification priority flag to a priority value. 388 if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) { 389 n.priority = Notification.PRIORITY_MAX; 390 } 391 392 // Convert priority value to an importance value, used only for pre-channels notifications. 393 int requestedImportance = IMPORTANCE_DEFAULT; 394 n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN, 395 Notification.PRIORITY_MAX); 396 switch (n.priority) { 397 case Notification.PRIORITY_MIN: 398 requestedImportance = IMPORTANCE_MIN; 399 break; 400 case Notification.PRIORITY_LOW: 401 requestedImportance = IMPORTANCE_LOW; 402 break; 403 case Notification.PRIORITY_DEFAULT: 404 requestedImportance = IMPORTANCE_DEFAULT; 405 break; 406 case Notification.PRIORITY_HIGH: 407 case Notification.PRIORITY_MAX: 408 requestedImportance = IMPORTANCE_HIGH; 409 break; 410 } 411 stats.requestedImportance = requestedImportance; 412 stats.isNoisy = mSound != null || mVibration != null; 413 414 // For pre-channels notifications, apply system overrides and then use requestedImportance 415 // as importance. 416 if (mPreChannelsNotification 417 && (importance == IMPORTANCE_UNSPECIFIED 418 || (!getChannel().hasUserSetImportance()))) { 419 if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) { 420 requestedImportance = IMPORTANCE_LOW; 421 } 422 423 if (stats.isNoisy) { 424 if (requestedImportance < IMPORTANCE_DEFAULT) { 425 requestedImportance = IMPORTANCE_DEFAULT; 426 } 427 } 428 429 if (n.fullScreenIntent != null) { 430 requestedImportance = IMPORTANCE_HIGH; 431 } 432 importance = requestedImportance; 433 mInitialImportanceExplanationCode = 434 MetricsEvent.IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS; 435 } 436 437 stats.naturalImportance = importance; 438 return importance; 439 } 440 441 // copy any notes that the ranking system may have made before the update copyRankingInformation(NotificationRecord previous)442 public void copyRankingInformation(NotificationRecord previous) { 443 mContactAffinity = previous.mContactAffinity; 444 mRecentlyIntrusive = previous.mRecentlyIntrusive; 445 mPackagePriority = previous.mPackagePriority; 446 mPackageVisibility = previous.mPackageVisibility; 447 mIntercept = previous.mIntercept; 448 mHidden = previous.mHidden; 449 mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); 450 mCreationTimeMs = previous.mCreationTimeMs; 451 mVisibleSinceMs = previous.mVisibleSinceMs; 452 if (previous.getSbn().getOverrideGroupKey() != null && !getSbn().isAppGroup()) { 453 getSbn().setOverrideGroupKey(previous.getSbn().getOverrideGroupKey()); 454 } 455 // Don't copy importance information or mGlobalSortKey, recompute them. 456 } 457 getNotification()458 public Notification getNotification() { return getSbn().getNotification(); } getFlags()459 public int getFlags() { return getSbn().getNotification().flags; } getUser()460 public UserHandle getUser() { return getSbn().getUser(); } getKey()461 public String getKey() { return getSbn().getKey(); } 462 /** @deprecated Use {@link #getUser()} instead. */ getUserId()463 public int getUserId() { return getSbn().getUserId(); } getUid()464 public int getUid() { return getSbn().getUid(); } 465 dump(ProtoOutputStream proto, long fieldId, boolean redact, int state)466 void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) { 467 final long token = proto.start(fieldId); 468 469 proto.write(NotificationRecordProto.KEY, getSbn().getKey()); 470 proto.write(NotificationRecordProto.STATE, state); 471 if (getChannel() != null) { 472 proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId()); 473 } 474 proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null); 475 proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null); 476 proto.write(NotificationRecordProto.FLAGS, getSbn().getNotification().flags); 477 proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey()); 478 proto.write(NotificationRecordProto.IMPORTANCE, getImportance()); 479 if (getSound() != null) { 480 proto.write(NotificationRecordProto.SOUND, getSound().toString()); 481 } 482 if (getAudioAttributes() != null) { 483 getAudioAttributes().dumpDebug(proto, NotificationRecordProto.AUDIO_ATTRIBUTES); 484 } 485 proto.write(NotificationRecordProto.PACKAGE, getSbn().getPackageName()); 486 proto.write(NotificationRecordProto.DELEGATE_PACKAGE, getSbn().getOpPkg()); 487 488 proto.end(token); 489 } 490 formatRemoteViews(RemoteViews rv)491 String formatRemoteViews(RemoteViews rv) { 492 if (rv == null) return "null"; 493 return String.format("%s/0x%08x (%d bytes): %s", 494 rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString()); 495 } 496 497 @NeverCompile // Avoid size overhead of debugging code. dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)498 void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) { 499 final Notification notification = getSbn().getNotification(); 500 pw.println(prefix + this); 501 prefix = prefix + " "; 502 pw.println(prefix + "uid=" + getSbn().getUid() + " userId=" + getSbn().getUserId()); 503 pw.println(prefix + "opPkg=" + getSbn().getOpPkg()); 504 pw.println(prefix + "icon=" + notification.getSmallIcon()); 505 pw.println(prefix + "flags=" + Notification.flagsToString(notification.flags)); 506 pw.println(prefix + "originalFlags=" + Notification.flagsToString(mOriginalFlags)); 507 pw.println(prefix + "pri=" + notification.priority); 508 pw.println(prefix + "key=" + getSbn().getKey()); 509 pw.println(prefix + "seen=" + mStats.hasSeen()); 510 pw.println(prefix + "groupKey=" + getGroupKey()); 511 pw.println(prefix + "notification="); 512 dumpNotification(pw, prefix + prefix, notification, redact); 513 pw.println(prefix + "publicNotification="); 514 dumpNotification(pw, prefix + prefix, notification.publicVersion, redact); 515 pw.println(prefix + "stats=" + stats.toString()); 516 pw.println(prefix + "mContactAffinity=" + mContactAffinity); 517 pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive); 518 pw.println(prefix + "mPackagePriority=" + mPackagePriority); 519 pw.println(prefix + "mPackageVisibility=" + mPackageVisibility); 520 pw.println(prefix + "mSystemImportance=" 521 + NotificationListenerService.Ranking.importanceToString(mSystemImportance)); 522 pw.println(prefix + "mAsstImportance=" 523 + NotificationListenerService.Ranking.importanceToString(mAssistantImportance)); 524 pw.println(prefix + "mImportance=" 525 + NotificationListenerService.Ranking.importanceToString(mImportance)); 526 pw.println(prefix + "mImportanceExplanation=" + getImportanceExplanation()); 527 pw.println(prefix + "mProposedImportance=" 528 + NotificationListenerService.Ranking.importanceToString(mProposedImportance)); 529 pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); 530 pw.println(prefix + "mSensitiveContent=" + mSensitiveContent); 531 pw.println(prefix + "mIntercept=" + mIntercept); 532 pw.println(prefix + "mHidden==" + mHidden); 533 pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); 534 pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs); 535 pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs); 536 pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs); 537 pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs); 538 pw.println(prefix + "mInterruptionTimeMs=" + mInterruptionTimeMs); 539 pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects); 540 if (mPreChannelsNotification) { 541 pw.println(prefix + "defaults=" + Notification.defaultsToString(notification.defaults)); 542 pw.println(prefix + "n.sound=" + notification.sound); 543 pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType); 544 pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes); 545 pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", 546 notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); 547 pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate)); 548 } 549 pw.println(prefix + "mSound= " + mSound); 550 pw.println(prefix + "mVibration= " + mVibration); 551 pw.println(prefix + "mAttributes= " + mAttributes); 552 pw.println(prefix + "mLight= " + mLight); 553 pw.println(prefix + "mShowBadge=" + mShowBadge); 554 pw.println(prefix + "mColorized=" + notification.isColorized()); 555 pw.println(prefix + "mAllowBubble=" + mAllowBubble); 556 pw.println(prefix + "isBubble=" + notification.isBubbleNotification()); 557 pw.println(prefix + "mIsInterruptive=" + mIsInterruptive); 558 pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); 559 if (getPeopleOverride() != null) { 560 pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); 561 } 562 if (getSnoozeCriteria() != null) { 563 pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); 564 } 565 pw.println(prefix + "mAdjustments=" + mAdjustments); 566 pw.println(prefix + "shortcut=" + notification.getShortcutId() 567 + " found valid? " + (mShortcutInfo != null)); 568 pw.println(prefix + "mUserVisOverride=" + getPackageVisibilityOverride()); 569 } 570 dumpNotification(PrintWriter pw, String prefix, Notification notification, boolean redact)571 private void dumpNotification(PrintWriter pw, String prefix, Notification notification, 572 boolean redact) { 573 if (notification == null) { 574 pw.println(prefix + "None"); 575 return; 576 577 } 578 pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent); 579 pw.println(prefix + "contentIntent=" + notification.contentIntent); 580 pw.println(prefix + "deleteIntent=" + notification.deleteIntent); 581 pw.println(prefix + "number=" + notification.number); 582 pw.println(prefix + "groupAlertBehavior=" + notification.getGroupAlertBehavior()); 583 pw.println(prefix + "when=" + notification.when + "/" + notification.getWhen()); 584 585 pw.print(prefix + "tickerText="); 586 if (!TextUtils.isEmpty(notification.tickerText)) { 587 final String ticker = notification.tickerText.toString(); 588 if (redact) { 589 // if the string is long enough, we allow ourselves a few bytes for debugging 590 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : ""); 591 pw.println("..."); 592 } else { 593 pw.println(ticker); 594 } 595 } else { 596 pw.println("null"); 597 } 598 pw.println(prefix + "vis=" + notification.visibility); 599 pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView)); 600 pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView)); 601 pw.println(prefix + "headsUpContentView=" 602 + formatRemoteViews(notification.headsUpContentView)); 603 pw.println(prefix + String.format("color=0x%08x", notification.color)); 604 pw.println(prefix + "timeout=" + Duration.ofMillis(notification.getTimeoutAfter())); 605 if (notification.actions != null && notification.actions.length > 0) { 606 pw.println(prefix + "actions={"); 607 final int N = notification.actions.length; 608 for (int i = 0; i < N; i++) { 609 final Notification.Action action = notification.actions[i]; 610 if (action != null) { 611 pw.println(String.format("%s [%d] \"%s\" -> %s", 612 prefix, 613 i, 614 action.title, 615 action.actionIntent == null ? "null" : action.actionIntent.toString() 616 )); 617 } 618 } 619 pw.println(prefix + " }"); 620 } 621 if (notification.extras != null && notification.extras.size() > 0) { 622 pw.println(prefix + "extras={"); 623 for (String key : notification.extras.keySet()) { 624 pw.print(prefix + " " + key + "="); 625 Object val = notification.extras.get(key); 626 if (val == null) { 627 pw.println("null"); 628 } else { 629 pw.print(val.getClass().getSimpleName()); 630 if (redact && (val instanceof CharSequence) && shouldRedactStringExtra(key)) { 631 pw.print(String.format(" [length=%d]", ((CharSequence) val).length())); 632 // redact contents from bugreports 633 } else if (val instanceof Bitmap) { 634 pw.print(String.format(" (%dx%d)", 635 ((Bitmap) val).getWidth(), 636 ((Bitmap) val).getHeight())); 637 } else if (val.getClass().isArray()) { 638 final int N = Array.getLength(val); 639 pw.print(" (" + N + ")"); 640 if (!redact) { 641 for (int j = 0; j < N; j++) { 642 pw.println(); 643 pw.print(String.format("%s [%d] %s", 644 prefix, j, String.valueOf(Array.get(val, j)))); 645 } 646 } 647 } else { 648 pw.print(" (" + String.valueOf(val) + ")"); 649 } 650 pw.println(); 651 } 652 } 653 pw.println(prefix + "}"); 654 } 655 } 656 shouldRedactStringExtra(String key)657 private boolean shouldRedactStringExtra(String key) { 658 if (key == null) return true; 659 switch (key) { 660 // none of these keys contain user-related information; they do not need to be redacted 661 case Notification.EXTRA_SUBSTITUTE_APP_NAME: 662 case Notification.EXTRA_TEMPLATE: 663 case "android.support.v4.app.extra.COMPAT_TEMPLATE": 664 return false; 665 default: 666 return true; 667 } 668 } 669 670 @Override toString()671 public final String toString() { 672 return String.format( 673 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" + 674 ": %s)", 675 System.identityHashCode(this), 676 this.getSbn().getPackageName(), this.getSbn().getUser(), this.getSbn().getId(), 677 this.getSbn().getTag(), this.mImportance, this.getSbn().getKey(), 678 this.getSbn().getNotification()); 679 } 680 hasAdjustment(String key)681 public boolean hasAdjustment(String key) { 682 synchronized (mAdjustments) { 683 for (Adjustment adjustment : mAdjustments) { 684 if (adjustment.getSignals().containsKey(key)) { 685 return true; 686 } 687 } 688 } 689 return false; 690 } 691 addAdjustment(Adjustment adjustment)692 public void addAdjustment(Adjustment adjustment) { 693 synchronized (mAdjustments) { 694 mAdjustments.add(adjustment); 695 } 696 } 697 applyAdjustments()698 public void applyAdjustments() { 699 long now = System.currentTimeMillis(); 700 synchronized (mAdjustments) { 701 for (Adjustment adjustment: mAdjustments) { 702 Bundle signals = adjustment.getSignals(); 703 if (signals.containsKey(Adjustment.KEY_PEOPLE)) { 704 final ArrayList<String> people = 705 adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE); 706 setPeopleOverride(people); 707 EventLogTags.writeNotificationAdjusted( 708 getKey(), Adjustment.KEY_PEOPLE, people.toString()); 709 } 710 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) { 711 final ArrayList<SnoozeCriterion> snoozeCriterionList = 712 adjustment.getSignals().getParcelableArrayList( 713 Adjustment.KEY_SNOOZE_CRITERIA, android.service.notification.SnoozeCriterion.class); 714 setSnoozeCriteria(snoozeCriterionList); 715 EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA, 716 snoozeCriterionList.toString()); 717 } 718 if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) { 719 final String groupOverrideKey = 720 adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY); 721 setOverrideGroupKey(groupOverrideKey); 722 EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_GROUP_KEY, 723 groupOverrideKey); 724 } 725 if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) { 726 // Only allow user sentiment update from assistant if user hasn't already 727 // expressed a preference for this channel 728 if (!mIsAppImportanceLocked 729 && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) { 730 setUserSentiment(adjustment.getSignals().getInt( 731 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL)); 732 EventLogTags.writeNotificationAdjusted(getKey(), 733 Adjustment.KEY_USER_SENTIMENT, 734 Integer.toString(getUserSentiment())); 735 } 736 } 737 if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) { 738 setSystemGeneratedSmartActions( 739 signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, android.app.Notification.Action.class)); 740 EventLogTags.writeNotificationAdjusted(getKey(), 741 Adjustment.KEY_CONTEXTUAL_ACTIONS, 742 getSystemGeneratedSmartActions().toString()); 743 } 744 if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) { 745 setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES)); 746 EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_TEXT_REPLIES, 747 getSmartReplies().toString()); 748 } 749 if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) { 750 int importance = signals.getInt(Adjustment.KEY_IMPORTANCE); 751 importance = Math.max(IMPORTANCE_UNSPECIFIED, importance); 752 importance = Math.min(IMPORTANCE_HIGH, importance); 753 setAssistantImportance(importance); 754 EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_IMPORTANCE, 755 Integer.toString(importance)); 756 } 757 if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) { 758 mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE); 759 EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_RANKING_SCORE, 760 Float.toString(mRankingScore)); 761 } 762 if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) { 763 mIsNotConversationOverride = signals.getBoolean( 764 Adjustment.KEY_NOT_CONVERSATION); 765 EventLogTags.writeNotificationAdjusted(getKey(), 766 Adjustment.KEY_NOT_CONVERSATION, 767 Boolean.toString(mIsNotConversationOverride)); 768 } 769 if (signals.containsKey(Adjustment.KEY_IMPORTANCE_PROPOSAL)) { 770 mProposedImportance = signals.getInt(Adjustment.KEY_IMPORTANCE_PROPOSAL); 771 EventLogTags.writeNotificationAdjusted(getKey(), 772 Adjustment.KEY_IMPORTANCE_PROPOSAL, 773 Integer.toString(mProposedImportance)); 774 } 775 if (signals.containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) { 776 mSensitiveContent = signals.getBoolean(Adjustment.KEY_SENSITIVE_CONTENT); 777 EventLogTags.writeNotificationAdjusted(getKey(), 778 Adjustment.KEY_SENSITIVE_CONTENT, 779 Boolean.toString(mSensitiveContent)); 780 } 781 if (!signals.isEmpty() && adjustment.getIssuer() != null) { 782 mAdjustmentIssuer = adjustment.getIssuer(); 783 } 784 } 785 // We have now gotten all the information out of the adjustments and can forget them. 786 mAdjustments.clear(); 787 } 788 } 789 getAdjustmentIssuer()790 String getAdjustmentIssuer() { 791 return mAdjustmentIssuer; 792 } 793 setIsAppImportanceLocked(boolean isAppImportanceLocked)794 public void setIsAppImportanceLocked(boolean isAppImportanceLocked) { 795 mIsAppImportanceLocked = isAppImportanceLocked; 796 calculateUserSentiment(); 797 } 798 setContactAffinity(float contactAffinity)799 public void setContactAffinity(float contactAffinity) { 800 mContactAffinity = contactAffinity; 801 } 802 getContactAffinity()803 public float getContactAffinity() { 804 return mContactAffinity; 805 } 806 setRecentlyIntrusive(boolean recentlyIntrusive)807 public void setRecentlyIntrusive(boolean recentlyIntrusive) { 808 mRecentlyIntrusive = recentlyIntrusive; 809 if (recentlyIntrusive) { 810 mLastIntrusive = System.currentTimeMillis(); 811 } 812 } 813 isRecentlyIntrusive()814 public boolean isRecentlyIntrusive() { 815 return mRecentlyIntrusive; 816 } 817 getLastIntrusive()818 public long getLastIntrusive() { 819 return mLastIntrusive; 820 } 821 setPackagePriority(int packagePriority)822 public void setPackagePriority(int packagePriority) { 823 mPackagePriority = packagePriority; 824 } 825 getPackagePriority()826 public int getPackagePriority() { 827 return mPackagePriority; 828 } 829 setPackageVisibilityOverride(int packageVisibility)830 public void setPackageVisibilityOverride(int packageVisibility) { 831 mPackageVisibility = packageVisibility; 832 } 833 getPackageVisibilityOverride()834 public int getPackageVisibilityOverride() { 835 return mPackageVisibility; 836 } 837 getUserExplanation()838 private String getUserExplanation() { 839 if (mUserExplanation == null) { 840 mUserExplanation = mContext.getResources().getString( 841 com.android.internal.R.string.importance_from_user); 842 } 843 return mUserExplanation; 844 } 845 846 /** 847 * Sets the importance value the system thinks the record should have. 848 * e.g. bumping up foreground service notifications or people to people notifications. 849 */ setSystemImportance(int importance)850 public void setSystemImportance(int importance) { 851 mSystemImportance = importance; 852 // System importance is only changed in enqueue, so it's ok for us to calculate the 853 // importance directly instead of waiting for signal extractor. 854 calculateImportance(); 855 } 856 857 /** 858 * Sets the importance value the 859 * {@link android.service.notification.NotificationAssistantService} thinks the record should 860 * have. 861 */ setAssistantImportance(int importance)862 public void setAssistantImportance(int importance) { 863 mAssistantImportance = importance; 864 // Unlike the system importance, the assistant importance can change on posted 865 // notifications, so don't calculateImportance() here, but wait for the signal extractors. 866 } 867 868 /** 869 * Returns the importance set by the assistant, or IMPORTANCE_UNSPECIFIED if the assistant 870 * hasn't set it. 871 */ getAssistantImportance()872 public int getAssistantImportance() { 873 return mAssistantImportance; 874 } 875 setImportanceFixed(boolean fixed)876 public void setImportanceFixed(boolean fixed) { 877 mImportanceFixed = fixed; 878 } 879 isImportanceFixed()880 public boolean isImportanceFixed() { 881 return mImportanceFixed; 882 } 883 884 /** 885 * Recalculates the importance of the record after fields affecting importance have changed, 886 * and records an explanation. 887 */ calculateImportance()888 protected void calculateImportance() { 889 mImportance = calculateInitialImportance(); 890 mImportanceExplanationCode = mInitialImportanceExplanationCode; 891 892 // Consider Notification Assistant and system overrides to importance. If both, system wins. 893 if (!getChannel().hasUserSetImportance() 894 && mAssistantImportance != IMPORTANCE_UNSPECIFIED 895 && !mImportanceFixed) { 896 mImportance = mAssistantImportance; 897 mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_ASST; 898 } 899 if (mSystemImportance != IMPORTANCE_UNSPECIFIED) { 900 mImportance = mSystemImportance; 901 mImportanceExplanationCode = MetricsEvent.IMPORTANCE_EXPLANATION_SYSTEM; 902 } 903 } 904 getImportance()905 public int getImportance() { 906 return mImportance; 907 } 908 getInitialImportance()909 int getInitialImportance() { 910 return stats.naturalImportance; 911 } 912 getProposedImportance()913 public int getProposedImportance() { 914 return mProposedImportance; 915 } 916 917 /** 918 * @return true if the notification contains sensitive content detected by the assistant. 919 */ hasSensitiveContent()920 public boolean hasSensitiveContent() { 921 return mSensitiveContent; 922 } 923 getRankingScore()924 public float getRankingScore() { 925 return mRankingScore; 926 } 927 getImportanceExplanationCode()928 int getImportanceExplanationCode() { 929 return mImportanceExplanationCode; 930 } 931 getInitialImportanceExplanationCode()932 int getInitialImportanceExplanationCode() { 933 return mInitialImportanceExplanationCode; 934 } 935 getImportanceExplanation()936 public CharSequence getImportanceExplanation() { 937 switch (mImportanceExplanationCode) { 938 case MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN: 939 return null; 940 case MetricsEvent.IMPORTANCE_EXPLANATION_APP: 941 case MetricsEvent.IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS: 942 return "app"; 943 case MetricsEvent.IMPORTANCE_EXPLANATION_USER: 944 return "user"; 945 case MetricsEvent.IMPORTANCE_EXPLANATION_ASST: 946 return "asst"; 947 case MetricsEvent.IMPORTANCE_EXPLANATION_SYSTEM: 948 return "system"; 949 } 950 return null; 951 } 952 setIntercepted(boolean intercept)953 public boolean setIntercepted(boolean intercept) { 954 mIntercept = intercept; 955 mInterceptSet = true; 956 return mIntercept; 957 } 958 959 /** 960 * Set to affect global sort key. 961 * 962 * @param criticality used in a string based sort thus 0 is the most critical 963 */ setCriticality(int criticality)964 public void setCriticality(int criticality) { 965 mCriticality = criticality; 966 } 967 getCriticality()968 public int getCriticality() { 969 return mCriticality; 970 } 971 isIntercepted()972 public boolean isIntercepted() { 973 return mIntercept; 974 } 975 hasInterceptBeenSet()976 public boolean hasInterceptBeenSet() { 977 return mInterceptSet; 978 } 979 isNewEnoughForAlerting(long now)980 public boolean isNewEnoughForAlerting(long now) { 981 return getFreshnessMs(now) <= MAX_SOUND_DELAY_MS; 982 } 983 setHidden(boolean hidden)984 public void setHidden(boolean hidden) { 985 mHidden = hidden; 986 } 987 isHidden()988 public boolean isHidden() { 989 return mHidden; 990 } 991 isForegroundService()992 public boolean isForegroundService() { 993 return 0 != (getFlags() & Notification.FLAG_FOREGROUND_SERVICE); 994 } 995 996 /** 997 * Override of all alerting information on the channel and notification. Used when notifications 998 * are reposted in response to direct user action and thus don't need to alert. 999 */ setPostSilently(boolean postSilently)1000 public void setPostSilently(boolean postSilently) { 1001 mPostSilently = postSilently; 1002 } 1003 shouldPostSilently()1004 public boolean shouldPostSilently() { 1005 return mPostSilently; 1006 } 1007 setSuppressedVisualEffects(int effects)1008 public void setSuppressedVisualEffects(int effects) { 1009 mSuppressedVisualEffects = effects; 1010 } 1011 getSuppressedVisualEffects()1012 public int getSuppressedVisualEffects() { 1013 return mSuppressedVisualEffects; 1014 } 1015 isCategory(String category)1016 public boolean isCategory(String category) { 1017 return Objects.equals(getNotification().category, category); 1018 } 1019 isAudioAttributesUsage(int usage)1020 public boolean isAudioAttributesUsage(int usage) { 1021 return mAttributes.getUsage() == usage; 1022 } 1023 1024 /** 1025 * Returns the timestamp to use for time-based sorting in the ranker. 1026 */ getRankingTimeMs()1027 public long getRankingTimeMs() { 1028 return mRankingTimeMs; 1029 } 1030 1031 /** 1032 * @param now this current time in milliseconds. 1033 * @returns the number of milliseconds since the most recent update, or the post time if none. 1034 */ getFreshnessMs(long now)1035 public int getFreshnessMs(long now) { 1036 return (int) (now - mUpdateTimeMs); 1037 } 1038 1039 /** 1040 * @param now this current time in milliseconds. 1041 * @returns the number of milliseconds since the the first post, ignoring updates. 1042 */ getLifespanMs(long now)1043 public int getLifespanMs(long now) { 1044 return (int) (now - mCreationTimeMs); 1045 } 1046 1047 /** 1048 * @param now this current time in milliseconds. 1049 * @returns the number of milliseconds since the most recent visibility event, or 0 if never. 1050 */ getExposureMs(long now)1051 public int getExposureMs(long now) { 1052 return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs); 1053 } 1054 getInterruptionMs(long now)1055 public int getInterruptionMs(long now) { 1056 return (int) (now - mInterruptionTimeMs); 1057 } 1058 getUpdateTimeMs()1059 public long getUpdateTimeMs() { 1060 return mUpdateTimeMs; 1061 } 1062 1063 /** 1064 * Set the visibility of the notification. 1065 */ setVisibility(boolean visible, int rank, int count, NotificationRecordLogger notificationRecordLogger)1066 public void setVisibility(boolean visible, int rank, int count, 1067 NotificationRecordLogger notificationRecordLogger) { 1068 final long now = System.currentTimeMillis(); 1069 mVisibleSinceMs = visible ? now : mVisibleSinceMs; 1070 stats.onVisibilityChanged(visible); 1071 MetricsLogger.action(getLogMaker(now) 1072 .setCategory(MetricsEvent.NOTIFICATION_ITEM) 1073 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) 1074 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank) 1075 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count)); 1076 if (visible) { 1077 setSeen(); 1078 MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now)); 1079 } 1080 EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0, 1081 getLifespanMs(now), 1082 getFreshnessMs(now), 1083 0, // exposure time 1084 rank); 1085 notificationRecordLogger.logNotificationVisibility(this, visible); 1086 } 1087 1088 /** 1089 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()} 1090 * of the previous notification record, 0 otherwise 1091 */ calculateRankingTimeMs(long previousRankingTimeMs)1092 private long calculateRankingTimeMs(long previousRankingTimeMs) { 1093 Notification n = getNotification(); 1094 // Take developer provided 'when', unless it's in the future. 1095 if (sortSectionByTime()) { 1096 if (n.hasAppProvidedWhen() && n.getWhen() <= getSbn().getPostTime()){ 1097 return n.getWhen(); 1098 } 1099 } else { 1100 if (n.when != 0 && n.when <= getSbn().getPostTime()) { 1101 return n.when; 1102 } 1103 } 1104 // If we've ranked a previous instance with a timestamp, inherit it. This case is 1105 // important in order to have ranking stability for updating notifications. 1106 if (previousRankingTimeMs > 0) { 1107 return previousRankingTimeMs; 1108 } 1109 return getSbn().getPostTime(); 1110 } 1111 setGlobalSortKey(String globalSortKey)1112 public void setGlobalSortKey(String globalSortKey) { 1113 mGlobalSortKey = globalSortKey; 1114 } 1115 getGlobalSortKey()1116 public String getGlobalSortKey() { 1117 return mGlobalSortKey; 1118 } 1119 1120 /** Check if any of the listeners have marked this notification as seen by the user. */ isSeen()1121 public boolean isSeen() { 1122 return mStats.hasSeen(); 1123 } 1124 1125 /** Mark the notification as seen by the user. */ setSeen()1126 public void setSeen() { 1127 mStats.setSeen(); 1128 if (mTextChanged) { 1129 setInterruptive(true); 1130 } 1131 } 1132 setAuthoritativeRank(int authoritativeRank)1133 public void setAuthoritativeRank(int authoritativeRank) { 1134 mAuthoritativeRank = authoritativeRank; 1135 } 1136 getAuthoritativeRank()1137 public int getAuthoritativeRank() { 1138 return mAuthoritativeRank; 1139 } 1140 getGroupKey()1141 public String getGroupKey() { 1142 return getSbn().getGroupKey(); 1143 } 1144 setOverrideGroupKey(String overrideGroupKey)1145 public void setOverrideGroupKey(String overrideGroupKey) { 1146 getSbn().setOverrideGroupKey(overrideGroupKey); 1147 } 1148 getChannel()1149 public NotificationChannel getChannel() { 1150 return mChannel; 1151 } 1152 1153 /** 1154 * @see PermissionHelper#isPermissionUserSet(String, int) 1155 */ getIsAppImportanceLocked()1156 public boolean getIsAppImportanceLocked() { 1157 return mIsAppImportanceLocked; 1158 } 1159 updateNotificationChannel(NotificationChannel channel)1160 protected void updateNotificationChannel(NotificationChannel channel) { 1161 if (channel != null) { 1162 mChannel = channel; 1163 calculateImportance(); 1164 calculateUserSentiment(); 1165 mVibration = calculateVibration(); 1166 if (restrictAudioAttributesCall() || restrictAudioAttributesAlarm() 1167 || restrictAudioAttributesMedia()) { 1168 if (channel.getAudioAttributes() != null) { 1169 mAttributes = channel.getAudioAttributes(); 1170 } else { 1171 mAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; 1172 } 1173 } 1174 } 1175 } 1176 setShowBadge(boolean showBadge)1177 public void setShowBadge(boolean showBadge) { 1178 mShowBadge = showBadge; 1179 } 1180 canBubble()1181 public boolean canBubble() { 1182 return mAllowBubble; 1183 } 1184 setAllowBubble(boolean allow)1185 public void setAllowBubble(boolean allow) { 1186 mAllowBubble = allow; 1187 } 1188 canShowBadge()1189 public boolean canShowBadge() { 1190 return mShowBadge; 1191 } 1192 getLight()1193 public Light getLight() { 1194 return mLight; 1195 } 1196 getSound()1197 public Uri getSound() { 1198 return mSound; 1199 } 1200 getVibration()1201 public VibrationEffect getVibration() { 1202 return mVibration; 1203 } 1204 getAudioAttributes()1205 public @NonNull AudioAttributes getAudioAttributes() { 1206 return mAttributes; 1207 } 1208 getPeopleOverride()1209 public ArrayList<String> getPeopleOverride() { 1210 return mPeopleOverride; 1211 } 1212 resetRankingTime()1213 public void resetRankingTime() { 1214 if (sortSectionByTime()) { 1215 mRankingTimeMs = calculateRankingTimeMs(getSbn().getPostTime()); 1216 } 1217 } 1218 setInterruptive(boolean interruptive)1219 public void setInterruptive(boolean interruptive) { 1220 mIsInterruptive = interruptive; 1221 final long now = System.currentTimeMillis(); 1222 mInterruptionTimeMs = interruptive ? now : mInterruptionTimeMs; 1223 1224 if (interruptive) { 1225 MetricsLogger.action(getLogMaker() 1226 .setCategory(MetricsEvent.NOTIFICATION_INTERRUPTION) 1227 .setType(MetricsEvent.TYPE_OPEN) 1228 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS, 1229 getInterruptionMs(now))); 1230 MetricsLogger.histogram(mContext, "note_interruptive", getInterruptionMs(now)); 1231 } 1232 } 1233 setAudiblyAlerted(boolean audiblyAlerted)1234 public void setAudiblyAlerted(boolean audiblyAlerted) { 1235 mLastAudiblyAlertedMs = audiblyAlerted ? System.currentTimeMillis() : -1; 1236 } 1237 setTextChanged(boolean textChanged)1238 public void setTextChanged(boolean textChanged) { 1239 mTextChanged = textChanged; 1240 } 1241 setRecordedInterruption(boolean recorded)1242 public void setRecordedInterruption(boolean recorded) { 1243 mRecordedInterruption = recorded; 1244 } 1245 hasRecordedInterruption()1246 public boolean hasRecordedInterruption() { 1247 return mRecordedInterruption; 1248 } 1249 isInterruptive()1250 public boolean isInterruptive() { 1251 return mIsInterruptive; 1252 } 1253 isTextChanged()1254 public boolean isTextChanged() { 1255 return mTextChanged; 1256 } 1257 1258 /** Returns the time the notification audibly alerted the user. */ getLastAudiblyAlertedMs()1259 public long getLastAudiblyAlertedMs() { 1260 return mLastAudiblyAlertedMs; 1261 } 1262 setPeopleOverride(ArrayList<String> people)1263 protected void setPeopleOverride(ArrayList<String> people) { 1264 mPeopleOverride = people; 1265 } 1266 getSnoozeCriteria()1267 public ArrayList<SnoozeCriterion> getSnoozeCriteria() { 1268 return mSnoozeCriteria; 1269 } 1270 setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria)1271 protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) { 1272 mSnoozeCriteria = snoozeCriteria; 1273 } 1274 calculateUserSentiment()1275 private void calculateUserSentiment() { 1276 if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0 1277 || mIsAppImportanceLocked) { 1278 mUserSentiment = USER_SENTIMENT_POSITIVE; 1279 } 1280 } 1281 setUserSentiment(int userSentiment)1282 private void setUserSentiment(int userSentiment) { 1283 mUserSentiment = userSentiment; 1284 } 1285 getUserSentiment()1286 public int getUserSentiment() { 1287 return mUserSentiment; 1288 } 1289 getStats()1290 public NotificationStats getStats() { 1291 return mStats; 1292 } 1293 recordExpanded()1294 public void recordExpanded() { 1295 mStats.setExpanded(); 1296 } 1297 1298 /** Run when the notification is direct replied. */ recordDirectReplied()1299 public void recordDirectReplied() { 1300 if (Flags.lifetimeExtensionRefactor()) { 1301 // Mark the NotificationRecord as lifetime extended. 1302 Notification notification = getSbn().getNotification(); 1303 notification.flags |= Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; 1304 } 1305 1306 mStats.setDirectReplied(); 1307 } 1308 1309 1310 /** Run when the notification is smart replied. */ 1311 @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) recordSmartReplied()1312 public void recordSmartReplied() { 1313 Notification notification = getSbn().getNotification(); 1314 notification.flags |= Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; 1315 1316 mStats.setSmartReplied(); 1317 } 1318 recordDismissalSurface(@otificationStats.DismissalSurface int surface)1319 public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) { 1320 mStats.setDismissalSurface(surface); 1321 } 1322 recordDismissalSentiment(@otificationStats.DismissalSentiment int sentiment)1323 public void recordDismissalSentiment(@NotificationStats.DismissalSentiment int sentiment) { 1324 mStats.setDismissalSentiment(sentiment); 1325 } 1326 recordSnoozed()1327 public void recordSnoozed() { 1328 mStats.setSnoozed(); 1329 } 1330 recordViewedSettings()1331 public void recordViewedSettings() { 1332 mStats.setViewedSettings(); 1333 } 1334 setNumSmartRepliesAdded(int noReplies)1335 public void setNumSmartRepliesAdded(int noReplies) { 1336 mNumberOfSmartRepliesAdded = noReplies; 1337 } 1338 getNumSmartRepliesAdded()1339 public int getNumSmartRepliesAdded() { 1340 return mNumberOfSmartRepliesAdded; 1341 } 1342 setNumSmartActionsAdded(int noActions)1343 public void setNumSmartActionsAdded(int noActions) { 1344 mNumberOfSmartActionsAdded = noActions; 1345 } 1346 getNumSmartActionsAdded()1347 public int getNumSmartActionsAdded() { 1348 return mNumberOfSmartActionsAdded; 1349 } 1350 setSuggestionsGeneratedByAssistant(boolean generatedByAssistant)1351 public void setSuggestionsGeneratedByAssistant(boolean generatedByAssistant) { 1352 mSuggestionsGeneratedByAssistant = generatedByAssistant; 1353 } 1354 getSuggestionsGeneratedByAssistant()1355 public boolean getSuggestionsGeneratedByAssistant() { 1356 return mSuggestionsGeneratedByAssistant; 1357 } 1358 getEditChoicesBeforeSending()1359 public boolean getEditChoicesBeforeSending() { 1360 return mEditChoicesBeforeSending; 1361 } 1362 setEditChoicesBeforeSending(boolean editChoicesBeforeSending)1363 public void setEditChoicesBeforeSending(boolean editChoicesBeforeSending) { 1364 mEditChoicesBeforeSending = editChoicesBeforeSending; 1365 } 1366 hasSeenSmartReplies()1367 public boolean hasSeenSmartReplies() { 1368 return mHasSeenSmartReplies; 1369 } 1370 setSeenSmartReplies(boolean hasSeenSmartReplies)1371 public void setSeenSmartReplies(boolean hasSeenSmartReplies) { 1372 mHasSeenSmartReplies = hasSeenSmartReplies; 1373 } 1374 1375 /** 1376 * Returns whether this notification has been visible and expanded at the same time. 1377 */ hasBeenVisiblyExpanded()1378 public boolean hasBeenVisiblyExpanded() { 1379 return stats.hasBeenVisiblyExpanded(); 1380 } 1381 1382 /** 1383 * When the bubble state on a notif changes due to user action (e.g. dismiss a bubble) then 1384 * this value is set until an update or bubble change event due to user action (e.g. create 1385 * bubble from sysui) 1386 **/ isFlagBubbleRemoved()1387 public boolean isFlagBubbleRemoved() { 1388 return mFlagBubbleRemoved; 1389 } 1390 setFlagBubbleRemoved(boolean flagBubbleRemoved)1391 public void setFlagBubbleRemoved(boolean flagBubbleRemoved) { 1392 mFlagBubbleRemoved = flagBubbleRemoved; 1393 } 1394 setSystemGeneratedSmartActions( ArrayList<Notification.Action> systemGeneratedSmartActions)1395 public void setSystemGeneratedSmartActions( 1396 ArrayList<Notification.Action> systemGeneratedSmartActions) { 1397 mSystemGeneratedSmartActions = systemGeneratedSmartActions; 1398 } 1399 getSystemGeneratedSmartActions()1400 public ArrayList<Notification.Action> getSystemGeneratedSmartActions() { 1401 return mSystemGeneratedSmartActions; 1402 } 1403 setSmartReplies(ArrayList<CharSequence> smartReplies)1404 public void setSmartReplies(ArrayList<CharSequence> smartReplies) { 1405 mSmartReplies = smartReplies; 1406 } 1407 getSmartReplies()1408 public ArrayList<CharSequence> getSmartReplies() { 1409 return mSmartReplies; 1410 } 1411 1412 /** 1413 * Returns whether this notification was posted by a secondary app 1414 */ isProxied()1415 public boolean isProxied() { 1416 return !Objects.equals(getSbn().getPackageName(), getSbn().getOpPkg()); 1417 } 1418 getNotificationType()1419 public int getNotificationType() { 1420 if (isConversation()) { 1421 return NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; 1422 } else if (getImportance() >= IMPORTANCE_DEFAULT) { 1423 return NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; 1424 } else { 1425 return NotificationListenerService.FLAG_FILTER_TYPE_SILENT; 1426 } 1427 } 1428 1429 /** 1430 * @return all {@link Uri} that should have permission granted to whoever 1431 * will be rendering it. This list has already been vetted to only 1432 * include {@link Uri} that the enqueuing app can grant. 1433 */ getGrantableUris()1434 public @Nullable ArraySet<Uri> getGrantableUris() { 1435 return mGrantableUris; 1436 } 1437 1438 /** 1439 * Collect all {@link Uri} that should have permission granted to whoever 1440 * will be rendering it. 1441 */ calculateGrantableUris()1442 private void calculateGrantableUris() { 1443 Trace.beginSection("NotificationRecord.calculateGrantableUris"); 1444 try { 1445 // We can't grant URI permissions from system. 1446 final int sourceUid = getSbn().getUid(); 1447 if (sourceUid == android.os.Process.SYSTEM_UID) return; 1448 1449 final Notification notification = getNotification(); 1450 notification.visitUris((uri) -> { 1451 visitGrantableUri(uri, false, false); 1452 }); 1453 1454 if (notification.getChannelId() != null) { 1455 NotificationChannel channel = getChannel(); 1456 if (channel != null) { 1457 visitGrantableUri(channel.getSound(), (channel.getUserLockedFields() 1458 & NotificationChannel.USER_LOCKED_SOUND) != 0, true); 1459 } 1460 } 1461 } finally { 1462 Trace.endSection(); 1463 } 1464 } 1465 1466 /** 1467 * Note the presence of a {@link Uri} that should have permission granted to 1468 * whoever will be rendering it. 1469 * <p> 1470 * If the enqueuing app has the ability to grant access, it will be added to 1471 * {@link #mGrantableUris}. Otherwise, this will either log or throw 1472 * {@link SecurityException} depending on target SDK of enqueuing app. 1473 */ visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound)1474 private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) { 1475 if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return; 1476 1477 if (mGrantableUris != null && mGrantableUris.contains(uri)) { 1478 return; // already verified this URI 1479 } 1480 1481 final int sourceUid = getSbn().getUid(); 1482 final long ident = Binder.clearCallingIdentity(); 1483 try { 1484 // This will throw a SecurityException if the caller can't grant. 1485 mUgmInternal.checkGrantUriPermission(sourceUid, null, 1486 ContentProvider.getUriWithoutUserId(uri), 1487 Intent.FLAG_GRANT_READ_URI_PERMISSION, 1488 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid))); 1489 1490 if (mGrantableUris == null) { 1491 mGrantableUris = new ArraySet<>(); 1492 } 1493 mGrantableUris.add(uri); 1494 } catch (SecurityException e) { 1495 if (!userOverriddenUri) { 1496 if (isSound) { 1497 mSound = Settings.System.DEFAULT_NOTIFICATION_URI; 1498 Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage()); 1499 } else { 1500 if (mTargetSdkVersion >= Build.VERSION_CODES.P) { 1501 throw e; 1502 } else { 1503 Log.w(TAG, 1504 "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage()); 1505 } 1506 } 1507 } 1508 } finally { 1509 Binder.restoreCallingIdentity(ident); 1510 } 1511 } 1512 getLogMaker(long now)1513 public LogMaker getLogMaker(long now) { 1514 LogMaker lm = getSbn().getLogMaker() 1515 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance) 1516 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now)) 1517 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now)) 1518 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now)) 1519 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_INTERRUPTION_MILLIS, 1520 getInterruptionMs(now)); 1521 // Record results of the calculateImportance() calculation if available. 1522 if (mImportanceExplanationCode != MetricsEvent.IMPORTANCE_EXPLANATION_UNKNOWN) { 1523 lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_EXPLANATION, 1524 mImportanceExplanationCode); 1525 // To avoid redundancy, we log the initial importance information only if it was 1526 // overridden. 1527 if (((mImportanceExplanationCode == MetricsEvent.IMPORTANCE_EXPLANATION_ASST) 1528 || (mImportanceExplanationCode == MetricsEvent.IMPORTANCE_EXPLANATION_SYSTEM)) 1529 && (stats.naturalImportance != IMPORTANCE_UNSPECIFIED)) { 1530 // stats.naturalImportance is due to one of the 3 sources of initial importance. 1531 lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL_EXPLANATION, 1532 mInitialImportanceExplanationCode); 1533 lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_INITIAL, 1534 stats.naturalImportance); 1535 } 1536 } 1537 // Log Assistant override if present, whether or not importance calculation is complete. 1538 if (mAssistantImportance != IMPORTANCE_UNSPECIFIED) { 1539 lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_IMPORTANCE_ASST, 1540 mAssistantImportance); 1541 } 1542 // Log the issuer of any adjustments that may have affected this notification. We only log 1543 // the hash here as NotificationItem events are frequent, and the number of NAS 1544 // implementations (and hence the chance of collisions) is low. 1545 if (mAdjustmentIssuer != null) { 1546 lm.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_ASSISTANT_SERVICE_HASH, 1547 mAdjustmentIssuer.hashCode()); 1548 } 1549 return lm; 1550 } 1551 getLogMaker()1552 public LogMaker getLogMaker() { 1553 return getLogMaker(System.currentTimeMillis()); 1554 } 1555 getItemLogMaker()1556 public LogMaker getItemLogMaker() { 1557 return getLogMaker().setCategory(MetricsEvent.NOTIFICATION_ITEM); 1558 } 1559 hasUndecoratedRemoteView()1560 public boolean hasUndecoratedRemoteView() { 1561 Notification notification = getNotification(); 1562 boolean hasDecoratedStyle = 1563 notification.isStyle(Notification.DecoratedCustomViewStyle.class) 1564 || notification.isStyle(Notification.DecoratedMediaCustomViewStyle.class); 1565 boolean hasCustomRemoteView = notification.contentView != null 1566 || notification.bigContentView != null 1567 || notification.headsUpContentView != null; 1568 return hasCustomRemoteView && !hasDecoratedStyle; 1569 } 1570 setShortcutInfo(ShortcutInfo shortcutInfo)1571 public void setShortcutInfo(ShortcutInfo shortcutInfo) { 1572 mShortcutInfo = shortcutInfo; 1573 } 1574 getShortcutInfo()1575 public ShortcutInfo getShortcutInfo() { 1576 return mShortcutInfo; 1577 } 1578 setHasSentValidMsg(boolean hasSentValidMsg)1579 public void setHasSentValidMsg(boolean hasSentValidMsg) { 1580 mHasSentValidMsg = hasSentValidMsg; 1581 } 1582 userDemotedAppFromConvoSpace(boolean userDemoted)1583 public void userDemotedAppFromConvoSpace(boolean userDemoted) { 1584 mAppDemotedFromConvo = userDemoted; 1585 } 1586 setPkgAllowedAsConvo(boolean allowedAsConvo)1587 public void setPkgAllowedAsConvo(boolean allowedAsConvo) { 1588 mPkgAllowedAsConvo = allowedAsConvo; 1589 } 1590 1591 /** 1592 * Whether this notification is a conversation notification. 1593 */ isConversation()1594 public boolean isConversation() { 1595 Notification notification = getNotification(); 1596 // user kicked it out of convo space 1597 if (mChannel.isDemoted() || mAppDemotedFromConvo) { 1598 return false; 1599 } 1600 // NAS kicked it out of notification space 1601 if (mIsNotConversationOverride) { 1602 return false; 1603 } 1604 if (!notification.isStyle(Notification.MessagingStyle.class)) { 1605 // some non-msgStyle notifs can temporarily appear in the conversation space if category 1606 // is right 1607 if (mPkgAllowedAsConvo && mTargetSdkVersion < Build.VERSION_CODES.R 1608 && Notification.CATEGORY_MESSAGE.equals(getNotification().category)) { 1609 return true; 1610 } 1611 return false; 1612 } 1613 1614 if (mTargetSdkVersion >= Build.VERSION_CODES.R 1615 && notification.isStyle(Notification.MessagingStyle.class) 1616 && (mShortcutInfo == null || isOnlyBots(mShortcutInfo.getPersons()))) { 1617 return false; 1618 } 1619 if (mHasSentValidMsg && mShortcutInfo == null) { 1620 return false; 1621 } 1622 return true; 1623 } 1624 1625 /** 1626 * Determines if the {@link ShortcutInfo#getPersons()} array includes only bots, for the purpose 1627 * of excluding that shortcut from the "conversations" section of the notification shade. If 1628 * the shortcut has no people, this returns false to allow the conversation into the shade, and 1629 * if there is any non-bot person we allow it as well. Otherwise, this is only bots and will 1630 * not count as a conversation. 1631 */ isOnlyBots(Person[] persons)1632 private boolean isOnlyBots(Person[] persons) { 1633 // Return false if there are no persons at all 1634 if (persons == null || persons.length == 0) { 1635 return false; 1636 } 1637 // Return false if there are any non-bot persons 1638 for (Person person : persons) { 1639 if (!person.isBot()) { 1640 return false; 1641 } 1642 } 1643 // Return true otherwise 1644 return true; 1645 } 1646 getSbn()1647 StatusBarNotification getSbn() { 1648 return sbn; 1649 } 1650 1651 /** 1652 * Returns whether this record's ranking score is approximately equal to otherScore 1653 * (the difference must be within 0.0001). 1654 */ rankingScoreMatches(float otherScore)1655 public boolean rankingScoreMatches(float otherScore) { 1656 return Math.abs(mRankingScore - otherScore) < 0.0001; 1657 } 1658 setPendingLogUpdate(boolean pendingLogUpdate)1659 protected void setPendingLogUpdate(boolean pendingLogUpdate) { 1660 mPendingLogUpdate = pendingLogUpdate; 1661 } 1662 1663 // If a caller of this function subsequently logs the update, they should also call 1664 // setPendingLogUpdate to false to make sure other callers don't also do so. hasPendingLogUpdate()1665 protected boolean hasPendingLogUpdate() { 1666 return mPendingLogUpdate; 1667 } 1668 1669 /** 1670 * Merge the given set of phone numbers into the list of phone numbers that 1671 * are cached on this notification record. 1672 */ mergePhoneNumbers(ArraySet<String> phoneNumbers)1673 public void mergePhoneNumbers(ArraySet<String> phoneNumbers) { 1674 // if the given phone numbers are null or empty then don't do anything 1675 if (phoneNumbers == null || phoneNumbers.size() == 0) { 1676 return; 1677 } 1678 // initialize if not already 1679 if (mPhoneNumbers == null) { 1680 mPhoneNumbers = new ArraySet<>(); 1681 } 1682 mPhoneNumbers.addAll(phoneNumbers); 1683 } 1684 getPhoneNumbers()1685 public ArraySet<String> getPhoneNumbers() { 1686 return mPhoneNumbers; 1687 } 1688 isLocked()1689 boolean isLocked() { 1690 return getKeyguardManager().isKeyguardLocked() 1691 || !mPowerManager.isInteractive(); // Unlocked AOD 1692 } 1693 1694 /** 1695 * For some early {@link NotificationRecord}, {@link KeyguardManager} can be {@code null} in 1696 * the constructor. Retrieve it again if it is null. 1697 */ getKeyguardManager()1698 private KeyguardManager getKeyguardManager() { 1699 if (mKeyguardManager == null) { 1700 mKeyguardManager = mContext.getSystemService(KeyguardManager.class); 1701 } 1702 return mKeyguardManager; 1703 } 1704 1705 @VisibleForTesting 1706 static final class Light { 1707 public final int color; 1708 public final int onMs; 1709 public final int offMs; 1710 Light(int color, int onMs, int offMs)1711 public Light(int color, int onMs, int offMs) { 1712 this.color = color; 1713 this.onMs = onMs; 1714 this.offMs = offMs; 1715 } 1716 1717 @Override equals(Object o)1718 public boolean equals(Object o) { 1719 if (this == o) return true; 1720 if (o == null || getClass() != o.getClass()) return false; 1721 1722 Light light = (Light) o; 1723 1724 if (color != light.color) return false; 1725 if (onMs != light.onMs) return false; 1726 return offMs == light.offMs; 1727 1728 } 1729 1730 @Override hashCode()1731 public int hashCode() { 1732 int result = color; 1733 result = 31 * result + onMs; 1734 result = 31 * result + offMs; 1735 return result; 1736 } 1737 1738 @Override toString()1739 public String toString() { 1740 return "Light{" + 1741 "color=" + color + 1742 ", onMs=" + onMs + 1743 ", offMs=" + offMs + 1744 '}'; 1745 } 1746 } 1747 } 1748