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