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 25 .USER_SENTIMENT_NEUTRAL; 26 import static android.service.notification.NotificationListenerService.Ranking 27 .USER_SENTIMENT_POSITIVE; 28 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.app.IActivityManager; 32 import android.app.Notification; 33 import android.app.NotificationChannel; 34 import android.content.ContentProvider; 35 import android.content.ContentResolver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.pm.PackageManager; 39 import android.content.pm.PackageManager.NameNotFoundException; 40 import android.content.pm.PackageManagerInternal; 41 import android.content.res.Resources; 42 import android.graphics.Bitmap; 43 import android.graphics.drawable.Icon; 44 import android.media.AudioAttributes; 45 import android.media.AudioSystem; 46 import android.metrics.LogMaker; 47 import android.net.Uri; 48 import android.os.Binder; 49 import android.os.Build; 50 import android.os.Bundle; 51 import android.os.IBinder; 52 import android.os.RemoteException; 53 import android.os.UserHandle; 54 import android.provider.Settings; 55 import android.service.notification.Adjustment; 56 import android.service.notification.NotificationListenerService; 57 import android.service.notification.NotificationRecordProto; 58 import android.service.notification.NotificationStats; 59 import android.service.notification.SnoozeCriterion; 60 import android.service.notification.StatusBarNotification; 61 import android.text.TextUtils; 62 import android.util.ArraySet; 63 import android.util.Log; 64 import android.util.TimeUtils; 65 import android.util.proto.ProtoOutputStream; 66 import android.widget.RemoteViews; 67 68 import com.android.internal.annotations.VisibleForTesting; 69 import com.android.internal.logging.MetricsLogger; 70 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 71 import com.android.server.EventLogTags; 72 import com.android.server.LocalServices; 73 74 import java.io.PrintWriter; 75 import java.lang.reflect.Array; 76 import java.util.ArrayList; 77 import java.util.Arrays; 78 import java.util.List; 79 import java.util.Objects; 80 81 /** 82 * Holds data about notifications that should not be shared with the 83 * {@link android.service.notification.NotificationListenerService}s. 84 * 85 * <p>These objects should not be mutated unless the code is synchronized 86 * on {@link NotificationManagerService#mNotificationLock}, and any 87 * modification should be followed by a sorting of that list.</p> 88 * 89 * <p>Is sortable by {@link NotificationComparator}.</p> 90 * 91 * {@hide} 92 */ 93 public final class NotificationRecord { 94 static final String TAG = "NotificationRecord"; 95 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 96 private static final int MAX_LOGTAG_LENGTH = 35; 97 final StatusBarNotification sbn; 98 IActivityManager mAm; 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 private long mUpdateTimeMs; 130 131 // Is this record an update of an old record? 132 public boolean isUpdate; 133 private int mPackagePriority; 134 135 private int mAuthoritativeRank; 136 private String mGlobalSortKey; 137 private int mPackageVisibility; 138 private int mUserImportance = IMPORTANCE_UNSPECIFIED; 139 private int mImportance = IMPORTANCE_UNSPECIFIED; 140 private CharSequence mImportanceExplanation = null; 141 142 private int mSuppressedVisualEffects = 0; 143 private String mUserExplanation; 144 private String mPeopleExplanation; 145 private boolean mPreChannelsNotification = true; 146 private Uri mSound; 147 private long[] mVibration; 148 private AudioAttributes mAttributes; 149 private NotificationChannel mChannel; 150 private ArrayList<String> mPeopleOverride; 151 private ArrayList<SnoozeCriterion> mSnoozeCriteria; 152 private boolean mShowBadge; 153 private LogMaker mLogMaker; 154 private Light mLight; 155 private String mGroupLogTag; 156 private String mChannelIdLogTag; 157 158 private final List<Adjustment> mAdjustments; 159 private final NotificationStats mStats; 160 private int mUserSentiment; 161 private boolean mIsInterruptive; 162 private boolean mTextChanged; 163 private boolean mRecordedInterruption; 164 private int mNumberOfSmartRepliesAdded; 165 private boolean mHasSeenSmartReplies; 166 /** 167 * Whether this notification (and its channels) should be considered user locked. Used in 168 * conjunction with user sentiment calculation. 169 */ 170 private boolean mIsAppImportanceLocked; 171 private ArraySet<Uri> mGrantableUris; 172 NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel)173 public NotificationRecord(Context context, StatusBarNotification sbn, 174 NotificationChannel channel) { 175 this.sbn = sbn; 176 mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class) 177 .getPackageTargetSdkVersion(sbn.getPackageName()); 178 mAm = ActivityManager.getService(); 179 mOriginalFlags = sbn.getNotification().flags; 180 mRankingTimeMs = calculateRankingTimeMs(0L); 181 mCreationTimeMs = sbn.getPostTime(); 182 mUpdateTimeMs = mCreationTimeMs; 183 mContext = context; 184 stats = new NotificationUsageStats.SingleNotificationStats(); 185 mChannel = channel; 186 mPreChannelsNotification = isPreChannelsNotification(); 187 mSound = calculateSound(); 188 mVibration = calculateVibration(); 189 mAttributes = calculateAttributes(); 190 mImportance = calculateImportance(); 191 mLight = calculateLights(); 192 mAdjustments = new ArrayList<>(); 193 mStats = new NotificationStats(); 194 calculateUserSentiment(); 195 calculateGrantableUris(); 196 } 197 isPreChannelsNotification()198 private boolean isPreChannelsNotification() { 199 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) { 200 if (mTargetSdkVersion < Build.VERSION_CODES.O) { 201 return true; 202 } 203 } 204 return false; 205 } 206 calculateSound()207 private Uri calculateSound() { 208 final Notification n = sbn.getNotification(); 209 210 // No notification sounds on tv 211 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 212 return null; 213 } 214 215 Uri sound = mChannel.getSound(); 216 if (mPreChannelsNotification && (getChannel().getUserLockedFields() 217 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 218 219 final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0; 220 if (useDefaultSound) { 221 sound = Settings.System.DEFAULT_NOTIFICATION_URI; 222 } else { 223 sound = n.sound; 224 } 225 } 226 return sound; 227 } 228 calculateLights()229 private Light calculateLights() { 230 int defaultLightColor = mContext.getResources().getColor( 231 com.android.internal.R.color.config_defaultNotificationColor); 232 int defaultLightOn = mContext.getResources().getInteger( 233 com.android.internal.R.integer.config_defaultNotificationLedOn); 234 int defaultLightOff = mContext.getResources().getInteger( 235 com.android.internal.R.integer.config_defaultNotificationLedOff); 236 237 int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor() 238 : defaultLightColor; 239 Light light = getChannel().shouldShowLights() ? new Light(channelLightColor, 240 defaultLightOn, defaultLightOff) : null; 241 if (mPreChannelsNotification 242 && (getChannel().getUserLockedFields() 243 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { 244 final Notification notification = sbn.getNotification(); 245 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { 246 light = new Light(notification.ledARGB, notification.ledOnMS, 247 notification.ledOffMS); 248 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) { 249 light = new Light(defaultLightColor, defaultLightOn, 250 defaultLightOff); 251 } 252 } else { 253 light = null; 254 } 255 } 256 return light; 257 } 258 calculateVibration()259 private long[] calculateVibration() { 260 long[] vibration; 261 final long[] defaultVibration = NotificationManagerService.getLongArray( 262 mContext.getResources(), 263 com.android.internal.R.array.config_defaultNotificationVibePattern, 264 NotificationManagerService.VIBRATE_PATTERN_MAXLEN, 265 NotificationManagerService.DEFAULT_VIBRATE_PATTERN); 266 if (getChannel().shouldVibrate()) { 267 vibration = getChannel().getVibrationPattern() == null 268 ? defaultVibration : getChannel().getVibrationPattern(); 269 } else { 270 vibration = null; 271 } 272 if (mPreChannelsNotification 273 && (getChannel().getUserLockedFields() 274 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { 275 final Notification notification = sbn.getNotification(); 276 final boolean useDefaultVibrate = 277 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 278 if (useDefaultVibrate) { 279 vibration = defaultVibration; 280 } else { 281 vibration = notification.vibrate; 282 } 283 } 284 return vibration; 285 } 286 calculateAttributes()287 private AudioAttributes calculateAttributes() { 288 final Notification n = sbn.getNotification(); 289 AudioAttributes attributes = getChannel().getAudioAttributes(); 290 if (attributes == null) { 291 attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; 292 } 293 294 if (mPreChannelsNotification 295 && (getChannel().getUserLockedFields() 296 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 297 if (n.audioAttributes != null) { 298 // prefer audio attributes to stream type 299 attributes = n.audioAttributes; 300 } else if (n.audioStreamType >= 0 301 && n.audioStreamType < AudioSystem.getNumStreamTypes()) { 302 // the stream type is valid, use it 303 attributes = new AudioAttributes.Builder() 304 .setInternalLegacyStreamType(n.audioStreamType) 305 .build(); 306 } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) { 307 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType)); 308 } 309 } 310 return attributes; 311 } 312 calculateImportance()313 private int calculateImportance() { 314 final Notification n = sbn.getNotification(); 315 int importance = getChannel().getImportance(); 316 int requestedImportance = IMPORTANCE_DEFAULT; 317 318 // Migrate notification flags to scores 319 if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) { 320 n.priority = Notification.PRIORITY_MAX; 321 } 322 323 n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN, 324 Notification.PRIORITY_MAX); 325 switch (n.priority) { 326 case Notification.PRIORITY_MIN: 327 requestedImportance = IMPORTANCE_MIN; 328 break; 329 case Notification.PRIORITY_LOW: 330 requestedImportance = IMPORTANCE_LOW; 331 break; 332 case Notification.PRIORITY_DEFAULT: 333 requestedImportance = IMPORTANCE_DEFAULT; 334 break; 335 case Notification.PRIORITY_HIGH: 336 case Notification.PRIORITY_MAX: 337 requestedImportance = IMPORTANCE_HIGH; 338 break; 339 } 340 stats.requestedImportance = requestedImportance; 341 stats.isNoisy = mSound != null || mVibration != null; 342 343 if (mPreChannelsNotification 344 && (importance == IMPORTANCE_UNSPECIFIED 345 || (getChannel().getUserLockedFields() 346 & USER_LOCKED_IMPORTANCE) == 0)) { 347 if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) { 348 requestedImportance = IMPORTANCE_LOW; 349 } 350 351 if (stats.isNoisy) { 352 if (requestedImportance < IMPORTANCE_DEFAULT) { 353 requestedImportance = IMPORTANCE_DEFAULT; 354 } 355 } 356 357 if (n.fullScreenIntent != null) { 358 requestedImportance = IMPORTANCE_HIGH; 359 } 360 importance = requestedImportance; 361 } 362 363 stats.naturalImportance = importance; 364 return importance; 365 } 366 367 // copy any notes that the ranking system may have made before the update copyRankingInformation(NotificationRecord previous)368 public void copyRankingInformation(NotificationRecord previous) { 369 mContactAffinity = previous.mContactAffinity; 370 mRecentlyIntrusive = previous.mRecentlyIntrusive; 371 mPackagePriority = previous.mPackagePriority; 372 mPackageVisibility = previous.mPackageVisibility; 373 mIntercept = previous.mIntercept; 374 mHidden = previous.mHidden; 375 mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); 376 mCreationTimeMs = previous.mCreationTimeMs; 377 mVisibleSinceMs = previous.mVisibleSinceMs; 378 if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) { 379 sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey()); 380 } 381 // Don't copy importance information or mGlobalSortKey, recompute them. 382 } 383 getNotification()384 public Notification getNotification() { return sbn.getNotification(); } getFlags()385 public int getFlags() { return sbn.getNotification().flags; } getUser()386 public UserHandle getUser() { return sbn.getUser(); } getKey()387 public String getKey() { return sbn.getKey(); } 388 /** @deprecated Use {@link #getUser()} instead. */ getUserId()389 public int getUserId() { return sbn.getUserId(); } getUid()390 public int getUid() { return sbn.getUid(); } 391 dump(ProtoOutputStream proto, long fieldId, boolean redact, int state)392 void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) { 393 final long token = proto.start(fieldId); 394 395 proto.write(NotificationRecordProto.KEY, sbn.getKey()); 396 proto.write(NotificationRecordProto.STATE, state); 397 if (getChannel() != null) { 398 proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId()); 399 } 400 proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null); 401 proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null); 402 proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags); 403 proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey()); 404 proto.write(NotificationRecordProto.IMPORTANCE, getImportance()); 405 if (getSound() != null) { 406 proto.write(NotificationRecordProto.SOUND, getSound().toString()); 407 } 408 if (getAudioAttributes() != null) { 409 getAudioAttributes().writeToProto(proto, NotificationRecordProto.AUDIO_ATTRIBUTES); 410 } 411 412 proto.end(token); 413 } 414 formatRemoteViews(RemoteViews rv)415 String formatRemoteViews(RemoteViews rv) { 416 if (rv == null) return "null"; 417 return String.format("%s/0x%08x (%d bytes): %s", 418 rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString()); 419 } 420 dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)421 void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) { 422 final Notification notification = sbn.getNotification(); 423 final Icon icon = notification.getSmallIcon(); 424 String iconStr = String.valueOf(icon); 425 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 426 iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId()); 427 } 428 pw.println(prefix + this); 429 prefix = prefix + " "; 430 pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); 431 pw.println(prefix + "icon=" + iconStr); 432 pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags)); 433 pw.println(prefix + "pri=" + notification.priority); 434 pw.println(prefix + "key=" + sbn.getKey()); 435 pw.println(prefix + "seen=" + mStats.hasSeen()); 436 pw.println(prefix + "groupKey=" + getGroupKey()); 437 pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent); 438 pw.println(prefix + "contentIntent=" + notification.contentIntent); 439 pw.println(prefix + "deleteIntent=" + notification.deleteIntent); 440 441 pw.print(prefix + "tickerText="); 442 if (!TextUtils.isEmpty(notification.tickerText)) { 443 final String ticker = notification.tickerText.toString(); 444 if (redact) { 445 // if the string is long enough, we allow ourselves a few bytes for debugging 446 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : ""); 447 pw.println("..."); 448 } else { 449 pw.println(ticker); 450 } 451 } else { 452 pw.println("null"); 453 } 454 pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView)); 455 pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView)); 456 pw.println(prefix + "headsUpContentView=" 457 + formatRemoteViews(notification.headsUpContentView)); 458 pw.print(prefix + String.format("color=0x%08x", notification.color)); 459 pw.println(prefix + "timeout=" 460 + TimeUtils.formatForLogging(notification.getTimeoutAfter())); 461 if (notification.actions != null && notification.actions.length > 0) { 462 pw.println(prefix + "actions={"); 463 final int N = notification.actions.length; 464 for (int i = 0; i < N; i++) { 465 final Notification.Action action = notification.actions[i]; 466 if (action != null) { 467 pw.println(String.format("%s [%d] \"%s\" -> %s", 468 prefix, 469 i, 470 action.title, 471 action.actionIntent == null ? "null" : action.actionIntent.toString() 472 )); 473 } 474 } 475 pw.println(prefix + " }"); 476 } 477 if (notification.extras != null && notification.extras.size() > 0) { 478 pw.println(prefix + "extras={"); 479 for (String key : notification.extras.keySet()) { 480 pw.print(prefix + " " + key + "="); 481 Object val = notification.extras.get(key); 482 if (val == null) { 483 pw.println("null"); 484 } else { 485 pw.print(val.getClass().getSimpleName()); 486 if (redact && (val instanceof CharSequence || val instanceof String)) { 487 // redact contents from bugreports 488 } else if (val instanceof Bitmap) { 489 pw.print(String.format(" (%dx%d)", 490 ((Bitmap) val).getWidth(), 491 ((Bitmap) val).getHeight())); 492 } else if (val.getClass().isArray()) { 493 final int N = Array.getLength(val); 494 pw.print(" (" + N + ")"); 495 if (!redact) { 496 for (int j = 0; j < N; j++) { 497 pw.println(); 498 pw.print(String.format("%s [%d] %s", 499 prefix, j, String.valueOf(Array.get(val, j)))); 500 } 501 } 502 } else { 503 pw.print(" (" + String.valueOf(val) + ")"); 504 } 505 pw.println(); 506 } 507 } 508 pw.println(prefix + "}"); 509 } 510 pw.println(prefix + "stats=" + stats.toString()); 511 pw.println(prefix + "mContactAffinity=" + mContactAffinity); 512 pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive); 513 pw.println(prefix + "mPackagePriority=" + mPackagePriority); 514 pw.println(prefix + "mPackageVisibility=" + mPackageVisibility); 515 pw.println(prefix + "mUserImportance=" 516 + NotificationListenerService.Ranking.importanceToString(mUserImportance)); 517 pw.println(prefix + "mImportance=" 518 + NotificationListenerService.Ranking.importanceToString(mImportance)); 519 pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation); 520 pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked); 521 pw.println(prefix + "mIntercept=" + mIntercept); 522 pw.println(prefix + "mHidden==" + mHidden); 523 pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); 524 pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs); 525 pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs); 526 pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs); 527 pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs); 528 pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects); 529 if (mPreChannelsNotification) { 530 pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x", 531 notification.defaults, notification.flags)); 532 pw.println(prefix + "n.sound=" + notification.sound); 533 pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType); 534 pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes); 535 pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", 536 notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); 537 pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate)); 538 } 539 pw.println(prefix + "mSound= " + mSound); 540 pw.println(prefix + "mVibration= " + mVibration); 541 pw.println(prefix + "mAttributes= " + mAttributes); 542 pw.println(prefix + "mLight= " + mLight); 543 pw.println(prefix + "mShowBadge=" + mShowBadge); 544 pw.println(prefix + "mColorized=" + notification.isColorized()); 545 pw.println(prefix + "mIsInterruptive=" + mIsInterruptive); 546 pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); 547 if (getPeopleOverride() != null) { 548 pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); 549 } 550 if (getSnoozeCriteria() != null) { 551 pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); 552 } 553 pw.println(prefix + "mAdjustments=" + mAdjustments); 554 } 555 556 idDebugString(Context baseContext, String packageName, int id)557 static String idDebugString(Context baseContext, String packageName, int id) { 558 Context c; 559 560 if (packageName != null) { 561 try { 562 c = baseContext.createPackageContext(packageName, 0); 563 } catch (NameNotFoundException e) { 564 c = baseContext; 565 } 566 } else { 567 c = baseContext; 568 } 569 570 Resources r = c.getResources(); 571 try { 572 return r.getResourceName(id); 573 } catch (Resources.NotFoundException e) { 574 return "<name unknown>"; 575 } 576 } 577 578 @Override toString()579 public final String toString() { 580 return String.format( 581 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" + 582 "appImportanceLocked=%s: %s)", 583 System.identityHashCode(this), 584 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), 585 this.sbn.getTag(), this.mImportance, this.sbn.getKey(), 586 mIsAppImportanceLocked, this.sbn.getNotification()); 587 } 588 addAdjustment(Adjustment adjustment)589 public void addAdjustment(Adjustment adjustment) { 590 synchronized (mAdjustments) { 591 mAdjustments.add(adjustment); 592 } 593 } 594 applyAdjustments()595 public void applyAdjustments() { 596 synchronized (mAdjustments) { 597 for (Adjustment adjustment: mAdjustments) { 598 Bundle signals = adjustment.getSignals(); 599 if (signals.containsKey(Adjustment.KEY_PEOPLE)) { 600 final ArrayList<String> people = 601 adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE); 602 setPeopleOverride(people); 603 } 604 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) { 605 final ArrayList<SnoozeCriterion> snoozeCriterionList = 606 adjustment.getSignals().getParcelableArrayList( 607 Adjustment.KEY_SNOOZE_CRITERIA); 608 setSnoozeCriteria(snoozeCriterionList); 609 } 610 if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) { 611 final String groupOverrideKey = 612 adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY); 613 setOverrideGroupKey(groupOverrideKey); 614 } 615 if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) { 616 // Only allow user sentiment update from assistant if user hasn't already 617 // expressed a preference for this channel 618 if (!mIsAppImportanceLocked 619 && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) { 620 setUserSentiment(adjustment.getSignals().getInt( 621 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL)); 622 } 623 } 624 } 625 } 626 } 627 setIsAppImportanceLocked(boolean isAppImportanceLocked)628 public void setIsAppImportanceLocked(boolean isAppImportanceLocked) { 629 mIsAppImportanceLocked = isAppImportanceLocked; 630 calculateUserSentiment(); 631 } 632 setContactAffinity(float contactAffinity)633 public void setContactAffinity(float contactAffinity) { 634 mContactAffinity = contactAffinity; 635 if (mImportance < IMPORTANCE_DEFAULT && 636 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) { 637 setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation()); 638 } 639 } 640 getContactAffinity()641 public float getContactAffinity() { 642 return mContactAffinity; 643 } 644 setRecentlyIntrusive(boolean recentlyIntrusive)645 public void setRecentlyIntrusive(boolean recentlyIntrusive) { 646 mRecentlyIntrusive = recentlyIntrusive; 647 if (recentlyIntrusive) { 648 mLastIntrusive = System.currentTimeMillis(); 649 } 650 } 651 isRecentlyIntrusive()652 public boolean isRecentlyIntrusive() { 653 return mRecentlyIntrusive; 654 } 655 getLastIntrusive()656 public long getLastIntrusive() { 657 return mLastIntrusive; 658 } 659 setPackagePriority(int packagePriority)660 public void setPackagePriority(int packagePriority) { 661 mPackagePriority = packagePriority; 662 } 663 getPackagePriority()664 public int getPackagePriority() { 665 return mPackagePriority; 666 } 667 setPackageVisibilityOverride(int packageVisibility)668 public void setPackageVisibilityOverride(int packageVisibility) { 669 mPackageVisibility = packageVisibility; 670 } 671 getPackageVisibilityOverride()672 public int getPackageVisibilityOverride() { 673 return mPackageVisibility; 674 } 675 setUserImportance(int importance)676 public void setUserImportance(int importance) { 677 mUserImportance = importance; 678 applyUserImportance(); 679 } 680 getUserExplanation()681 private String getUserExplanation() { 682 if (mUserExplanation == null) { 683 mUserExplanation = mContext.getResources().getString( 684 com.android.internal.R.string.importance_from_user); 685 } 686 return mUserExplanation; 687 } 688 getPeopleExplanation()689 private String getPeopleExplanation() { 690 if (mPeopleExplanation == null) { 691 mPeopleExplanation = mContext.getResources().getString( 692 com.android.internal.R.string.importance_from_person); 693 } 694 return mPeopleExplanation; 695 } 696 applyUserImportance()697 private void applyUserImportance() { 698 if (mUserImportance != IMPORTANCE_UNSPECIFIED) { 699 mImportance = mUserImportance; 700 mImportanceExplanation = getUserExplanation(); 701 } 702 } 703 getUserImportance()704 public int getUserImportance() { 705 return mUserImportance; 706 } 707 setImportance(int importance, CharSequence explanation)708 public void setImportance(int importance, CharSequence explanation) { 709 if (importance != IMPORTANCE_UNSPECIFIED) { 710 mImportance = importance; 711 mImportanceExplanation = explanation; 712 } 713 applyUserImportance(); 714 } 715 getImportance()716 public int getImportance() { 717 return mImportance; 718 } 719 getImportanceExplanation()720 public CharSequence getImportanceExplanation() { 721 return mImportanceExplanation; 722 } 723 setIntercepted(boolean intercept)724 public boolean setIntercepted(boolean intercept) { 725 mIntercept = intercept; 726 return mIntercept; 727 } 728 isIntercepted()729 public boolean isIntercepted() { 730 return mIntercept; 731 } 732 setHidden(boolean hidden)733 public void setHidden(boolean hidden) { 734 mHidden = hidden; 735 } 736 isHidden()737 public boolean isHidden() { 738 return mHidden; 739 } 740 741 setSuppressedVisualEffects(int effects)742 public void setSuppressedVisualEffects(int effects) { 743 mSuppressedVisualEffects = effects; 744 } 745 getSuppressedVisualEffects()746 public int getSuppressedVisualEffects() { 747 return mSuppressedVisualEffects; 748 } 749 isCategory(String category)750 public boolean isCategory(String category) { 751 return Objects.equals(getNotification().category, category); 752 } 753 isAudioAttributesUsage(int usage)754 public boolean isAudioAttributesUsage(int usage) { 755 return mAttributes != null && mAttributes.getUsage() == usage; 756 } 757 758 /** 759 * Returns the timestamp to use for time-based sorting in the ranker. 760 */ getRankingTimeMs()761 public long getRankingTimeMs() { 762 return mRankingTimeMs; 763 } 764 765 /** 766 * @param now this current time in milliseconds. 767 * @returns the number of milliseconds since the most recent update, or the post time if none. 768 */ getFreshnessMs(long now)769 public int getFreshnessMs(long now) { 770 return (int) (now - mUpdateTimeMs); 771 } 772 773 /** 774 * @param now this current time in milliseconds. 775 * @returns the number of milliseconds since the the first post, ignoring updates. 776 */ getLifespanMs(long now)777 public int getLifespanMs(long now) { 778 return (int) (now - mCreationTimeMs); 779 } 780 781 /** 782 * @param now this current time in milliseconds. 783 * @returns the number of milliseconds since the most recent visibility event, or 0 if never. 784 */ getExposureMs(long now)785 public int getExposureMs(long now) { 786 return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs); 787 } 788 789 /** 790 * Set the visibility of the notification. 791 */ setVisibility(boolean visible, int rank, int count)792 public void setVisibility(boolean visible, int rank, int count) { 793 final long now = System.currentTimeMillis(); 794 mVisibleSinceMs = visible ? now : mVisibleSinceMs; 795 stats.onVisibilityChanged(visible); 796 MetricsLogger.action(getLogMaker(now) 797 .setCategory(MetricsEvent.NOTIFICATION_ITEM) 798 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) 799 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank) 800 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count)); 801 if (visible) { 802 setSeen(); 803 MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now)); 804 } 805 EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0, 806 getLifespanMs(now), 807 getFreshnessMs(now), 808 0, // exposure time 809 rank); 810 } 811 812 /** 813 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()} 814 * of the previous notification record, 0 otherwise 815 */ calculateRankingTimeMs(long previousRankingTimeMs)816 private long calculateRankingTimeMs(long previousRankingTimeMs) { 817 Notification n = getNotification(); 818 // Take developer provided 'when', unless it's in the future. 819 if (n.when != 0 && n.when <= sbn.getPostTime()) { 820 return n.when; 821 } 822 // If we've ranked a previous instance with a timestamp, inherit it. This case is 823 // important in order to have ranking stability for updating notifications. 824 if (previousRankingTimeMs > 0) { 825 return previousRankingTimeMs; 826 } 827 return sbn.getPostTime(); 828 } 829 setGlobalSortKey(String globalSortKey)830 public void setGlobalSortKey(String globalSortKey) { 831 mGlobalSortKey = globalSortKey; 832 } 833 getGlobalSortKey()834 public String getGlobalSortKey() { 835 return mGlobalSortKey; 836 } 837 838 /** Check if any of the listeners have marked this notification as seen by the user. */ isSeen()839 public boolean isSeen() { 840 return mStats.hasSeen(); 841 } 842 843 /** Mark the notification as seen by the user. */ setSeen()844 public void setSeen() { 845 mStats.setSeen(); 846 if (mTextChanged) { 847 mIsInterruptive = true; 848 } 849 } 850 setAuthoritativeRank(int authoritativeRank)851 public void setAuthoritativeRank(int authoritativeRank) { 852 mAuthoritativeRank = authoritativeRank; 853 } 854 getAuthoritativeRank()855 public int getAuthoritativeRank() { 856 return mAuthoritativeRank; 857 } 858 getGroupKey()859 public String getGroupKey() { 860 return sbn.getGroupKey(); 861 } 862 setOverrideGroupKey(String overrideGroupKey)863 public void setOverrideGroupKey(String overrideGroupKey) { 864 sbn.setOverrideGroupKey(overrideGroupKey); 865 mGroupLogTag = null; 866 } 867 getGroupLogTag()868 private String getGroupLogTag() { 869 if (mGroupLogTag == null) { 870 mGroupLogTag = shortenTag(sbn.getGroup()); 871 } 872 return mGroupLogTag; 873 } 874 getChannelIdLogTag()875 private String getChannelIdLogTag() { 876 if (mChannelIdLogTag == null) { 877 mChannelIdLogTag = shortenTag(mChannel.getId()); 878 } 879 return mChannelIdLogTag; 880 } 881 shortenTag(String longTag)882 private String shortenTag(String longTag) { 883 if (longTag == null) { 884 return null; 885 } 886 if (longTag.length() < MAX_LOGTAG_LENGTH) { 887 return longTag; 888 } else { 889 return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" + 890 Integer.toHexString(longTag.hashCode()); 891 } 892 } 893 getChannel()894 public NotificationChannel getChannel() { 895 return mChannel; 896 } 897 898 /** 899 * @see RankingHelper#getIsAppImportanceLocked(String, int) 900 */ getIsAppImportanceLocked()901 public boolean getIsAppImportanceLocked() { 902 return mIsAppImportanceLocked; 903 } 904 updateNotificationChannel(NotificationChannel channel)905 protected void updateNotificationChannel(NotificationChannel channel) { 906 if (channel != null) { 907 mChannel = channel; 908 calculateImportance(); 909 calculateUserSentiment(); 910 } 911 } 912 setShowBadge(boolean showBadge)913 public void setShowBadge(boolean showBadge) { 914 mShowBadge = showBadge; 915 } 916 canShowBadge()917 public boolean canShowBadge() { 918 return mShowBadge; 919 } 920 getLight()921 public Light getLight() { 922 return mLight; 923 } 924 getSound()925 public Uri getSound() { 926 return mSound; 927 } 928 getVibration()929 public long[] getVibration() { 930 return mVibration; 931 } 932 getAudioAttributes()933 public AudioAttributes getAudioAttributes() { 934 return mAttributes; 935 } 936 getPeopleOverride()937 public ArrayList<String> getPeopleOverride() { 938 return mPeopleOverride; 939 } 940 setInterruptive(boolean interruptive)941 public void setInterruptive(boolean interruptive) { 942 mIsInterruptive = interruptive; 943 } 944 setTextChanged(boolean textChanged)945 public void setTextChanged(boolean textChanged) { 946 mTextChanged = textChanged; 947 } 948 setRecordedInterruption(boolean recorded)949 public void setRecordedInterruption(boolean recorded) { 950 mRecordedInterruption = recorded; 951 } 952 hasRecordedInterruption()953 public boolean hasRecordedInterruption() { 954 return mRecordedInterruption; 955 } 956 isInterruptive()957 public boolean isInterruptive() { 958 return mIsInterruptive; 959 } 960 setPeopleOverride(ArrayList<String> people)961 protected void setPeopleOverride(ArrayList<String> people) { 962 mPeopleOverride = people; 963 } 964 getSnoozeCriteria()965 public ArrayList<SnoozeCriterion> getSnoozeCriteria() { 966 return mSnoozeCriteria; 967 } 968 setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria)969 protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) { 970 mSnoozeCriteria = snoozeCriteria; 971 } 972 calculateUserSentiment()973 private void calculateUserSentiment() { 974 if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0 975 || mIsAppImportanceLocked) { 976 mUserSentiment = USER_SENTIMENT_POSITIVE; 977 } 978 } 979 setUserSentiment(int userSentiment)980 private void setUserSentiment(int userSentiment) { 981 mUserSentiment = userSentiment; 982 } 983 getUserSentiment()984 public int getUserSentiment() { 985 return mUserSentiment; 986 } 987 getStats()988 public NotificationStats getStats() { 989 return mStats; 990 } 991 recordExpanded()992 public void recordExpanded() { 993 mStats.setExpanded(); 994 } 995 recordDirectReplied()996 public void recordDirectReplied() { 997 mStats.setDirectReplied(); 998 } 999 recordDismissalSurface(@otificationStats.DismissalSurface int surface)1000 public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) { 1001 mStats.setDismissalSurface(surface); 1002 } 1003 recordSnoozed()1004 public void recordSnoozed() { 1005 mStats.setSnoozed(); 1006 } 1007 recordViewedSettings()1008 public void recordViewedSettings() { 1009 mStats.setViewedSettings(); 1010 } 1011 setNumSmartRepliesAdded(int noReplies)1012 public void setNumSmartRepliesAdded(int noReplies) { 1013 mNumberOfSmartRepliesAdded = noReplies; 1014 } 1015 getNumSmartRepliesAdded()1016 public int getNumSmartRepliesAdded() { 1017 return mNumberOfSmartRepliesAdded; 1018 } 1019 hasSeenSmartReplies()1020 public boolean hasSeenSmartReplies() { 1021 return mHasSeenSmartReplies; 1022 } 1023 setSeenSmartReplies(boolean hasSeenSmartReplies)1024 public void setSeenSmartReplies(boolean hasSeenSmartReplies) { 1025 mHasSeenSmartReplies = hasSeenSmartReplies; 1026 } 1027 1028 /** 1029 * @return all {@link Uri} that should have permission granted to whoever 1030 * will be rendering it. This list has already been vetted to only 1031 * include {@link Uri} that the enqueuing app can grant. 1032 */ getGrantableUris()1033 public @Nullable ArraySet<Uri> getGrantableUris() { 1034 return mGrantableUris; 1035 } 1036 1037 /** 1038 * Collect all {@link Uri} that should have permission granted to whoever 1039 * will be rendering it. 1040 */ calculateGrantableUris()1041 protected void calculateGrantableUris() { 1042 final Notification notification = getNotification(); 1043 notification.visitUris((uri) -> { 1044 visitGrantableUri(uri, false); 1045 }); 1046 1047 if (notification.getChannelId() != null) { 1048 NotificationChannel channel = getChannel(); 1049 if (channel != null) { 1050 visitGrantableUri(channel.getSound(), (channel.getUserLockedFields() 1051 & NotificationChannel.USER_LOCKED_SOUND) != 0); 1052 } 1053 } 1054 } 1055 1056 /** 1057 * Note the presence of a {@link Uri} that should have permission granted to 1058 * whoever will be rendering it. 1059 * <p> 1060 * If the enqueuing app has the ability to grant access, it will be added to 1061 * {@link #mGrantableUris}. Otherwise, this will either log or throw 1062 * {@link SecurityException} depending on target SDK of enqueuing app. 1063 */ visitGrantableUri(Uri uri, boolean userOverriddenUri)1064 private void visitGrantableUri(Uri uri, boolean userOverriddenUri) { 1065 if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return; 1066 1067 // We can't grant Uri permissions from system 1068 final int sourceUid = sbn.getUid(); 1069 if (sourceUid == android.os.Process.SYSTEM_UID) return; 1070 1071 final long ident = Binder.clearCallingIdentity(); 1072 try { 1073 // This will throw SecurityException if caller can't grant 1074 mAm.checkGrantUriPermission(sourceUid, null, 1075 ContentProvider.getUriWithoutUserId(uri), 1076 Intent.FLAG_GRANT_READ_URI_PERMISSION, 1077 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid))); 1078 1079 if (mGrantableUris == null) { 1080 mGrantableUris = new ArraySet<>(); 1081 } 1082 mGrantableUris.add(uri); 1083 } catch (RemoteException ignored) { 1084 // Ignored because we're in same process 1085 } catch (SecurityException e) { 1086 if (!userOverriddenUri) { 1087 if (mTargetSdkVersion >= Build.VERSION_CODES.P) { 1088 throw e; 1089 } else { 1090 Log.w(TAG, "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage()); 1091 } 1092 } 1093 } finally { 1094 Binder.restoreCallingIdentity(ident); 1095 } 1096 } 1097 getLogMaker(long now)1098 public LogMaker getLogMaker(long now) { 1099 if (mLogMaker == null) { 1100 // initialize fields that only change on update (so a new record) 1101 mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN) 1102 .setPackageName(sbn.getPackageName()) 1103 .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId()) 1104 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag()) 1105 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag()); 1106 } 1107 // reset fields that can change between updates, or are used by multiple logs 1108 return mLogMaker 1109 .clearCategory() 1110 .clearType() 1111 .clearSubtype() 1112 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX) 1113 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance) 1114 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag()) 1115 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY, 1116 sbn.getNotification().isGroupSummary() ? 1 : 0) 1117 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now)) 1118 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now)) 1119 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now)); 1120 } 1121 getLogMaker()1122 public LogMaker getLogMaker() { 1123 return getLogMaker(System.currentTimeMillis()); 1124 } 1125 1126 @VisibleForTesting 1127 static final class Light { 1128 public final int color; 1129 public final int onMs; 1130 public final int offMs; 1131 Light(int color, int onMs, int offMs)1132 public Light(int color, int onMs, int offMs) { 1133 this.color = color; 1134 this.onMs = onMs; 1135 this.offMs = offMs; 1136 } 1137 1138 @Override equals(Object o)1139 public boolean equals(Object o) { 1140 if (this == o) return true; 1141 if (o == null || getClass() != o.getClass()) return false; 1142 1143 Light light = (Light) o; 1144 1145 if (color != light.color) return false; 1146 if (onMs != light.onMs) return false; 1147 return offMs == light.offMs; 1148 1149 } 1150 1151 @Override hashCode()1152 public int hashCode() { 1153 int result = color; 1154 result = 31 * result + onMs; 1155 result = 31 * result + offMs; 1156 return result; 1157 } 1158 1159 @Override toString()1160 public String toString() { 1161 return "Light{" + 1162 "color=" + color + 1163 ", onMs=" + onMs + 1164 ", offMs=" + offMs + 1165 '}'; 1166 } 1167 } 1168 } 1169