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