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