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