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