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.NotificationManager.IMPORTANCE_MIN; 19 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 20 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 21 import static android.app.NotificationManager.IMPORTANCE_HIGH; 22 import static android.app.NotificationManager.IMPORTANCE_LOW; 23 24 import android.app.Notification; 25 import android.app.NotificationChannel; 26 import android.content.Context; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Resources; 31 import android.graphics.Bitmap; 32 import android.graphics.drawable.Icon; 33 import android.media.AudioAttributes; 34 import android.media.AudioSystem; 35 import android.metrics.LogMaker; 36 import android.net.Uri; 37 import android.os.Build; 38 import android.os.UserHandle; 39 import android.provider.Settings; 40 import android.service.notification.NotificationListenerService; 41 import android.service.notification.NotificationRecordProto; 42 import android.service.notification.SnoozeCriterion; 43 import android.service.notification.StatusBarNotification; 44 import android.text.TextUtils; 45 import android.util.Log; 46 import android.util.Slog; 47 import android.util.TimeUtils; 48 import android.util.proto.ProtoOutputStream; 49 import android.widget.RemoteViews; 50 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.internal.logging.MetricsLogger; 53 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 54 import com.android.server.EventLogTags; 55 56 import java.io.PrintWriter; 57 import java.lang.reflect.Array; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.Objects; 61 62 /** 63 * Holds data about notifications that should not be shared with the 64 * {@link android.service.notification.NotificationListenerService}s. 65 * 66 * <p>These objects should not be mutated unless the code is synchronized 67 * on {@link NotificationManagerService#mNotificationLock}, and any 68 * modification should be followed by a sorting of that list.</p> 69 * 70 * <p>Is sortable by {@link NotificationComparator}.</p> 71 * 72 * {@hide} 73 */ 74 public final class NotificationRecord { 75 static final String TAG = "NotificationRecord"; 76 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 77 private static final int MAX_LOGTAG_LENGTH = 35; 78 final StatusBarNotification sbn; 79 final int mOriginalFlags; 80 private final Context mContext; 81 82 NotificationUsageStats.SingleNotificationStats stats; 83 boolean isCanceled; 84 /** Whether the notification was seen by the user via one of the notification listeners. */ 85 boolean mIsSeen; 86 87 // These members are used by NotificationSignalExtractors 88 // to communicate with the ranking module. 89 private float mContactAffinity; 90 private boolean mRecentlyIntrusive; 91 private long mLastIntrusive; 92 93 // is this notification currently being intercepted by Zen Mode? 94 private boolean mIntercept; 95 96 // The timestamp used for ranking. 97 private long mRankingTimeMs; 98 99 // The first post time, stable across updates. 100 private long mCreationTimeMs; 101 102 // The most recent visibility event. 103 private long mVisibleSinceMs; 104 105 // The most recent update time, or the creation time if no updates. 106 private long mUpdateTimeMs; 107 108 // Is this record an update of an old record? 109 public boolean isUpdate; 110 private int mPackagePriority; 111 112 private int mAuthoritativeRank; 113 private String mGlobalSortKey; 114 private int mPackageVisibility; 115 private int mUserImportance = IMPORTANCE_UNSPECIFIED; 116 private int mImportance = IMPORTANCE_UNSPECIFIED; 117 private CharSequence mImportanceExplanation = null; 118 119 private int mSuppressedVisualEffects = 0; 120 private String mUserExplanation; 121 private String mPeopleExplanation; 122 private boolean mPreChannelsNotification = true; 123 private Uri mSound; 124 private long[] mVibration; 125 private AudioAttributes mAttributes; 126 private NotificationChannel mChannel; 127 private ArrayList<String> mPeopleOverride; 128 private ArrayList<SnoozeCriterion> mSnoozeCriteria; 129 private boolean mShowBadge; 130 private LogMaker mLogMaker; 131 private Light mLight; 132 private String mGroupLogTag; 133 private String mChannelIdLogTag; 134 135 @VisibleForTesting NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel)136 public NotificationRecord(Context context, StatusBarNotification sbn, 137 NotificationChannel channel) 138 { 139 this.sbn = sbn; 140 mOriginalFlags = sbn.getNotification().flags; 141 mRankingTimeMs = calculateRankingTimeMs(0L); 142 mCreationTimeMs = sbn.getPostTime(); 143 mUpdateTimeMs = mCreationTimeMs; 144 mContext = context; 145 stats = new NotificationUsageStats.SingleNotificationStats(); 146 mChannel = channel; 147 mPreChannelsNotification = isPreChannelsNotification(); 148 mSound = calculateSound(); 149 mVibration = calculateVibration(); 150 mAttributes = calculateAttributes(); 151 mImportance = calculateImportance(); 152 mLight = calculateLights(); 153 } 154 isPreChannelsNotification()155 private boolean isPreChannelsNotification() { 156 try { 157 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) { 158 final ApplicationInfo applicationInfo = 159 mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(), 160 0, UserHandle.getUserId(sbn.getUid())); 161 if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) { 162 return true; 163 } 164 } 165 } catch (NameNotFoundException e) { 166 Slog.e(TAG, "Can't find package", e); 167 } 168 return false; 169 } 170 calculateSound()171 private Uri calculateSound() { 172 final Notification n = sbn.getNotification(); 173 174 // No notification sounds on tv 175 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { 176 return null; 177 } 178 179 Uri sound = mChannel.getSound(); 180 if (mPreChannelsNotification && (getChannel().getUserLockedFields() 181 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 182 183 final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0; 184 if (useDefaultSound) { 185 sound = Settings.System.DEFAULT_NOTIFICATION_URI; 186 } else { 187 sound = n.sound; 188 } 189 } 190 return sound; 191 } 192 calculateLights()193 private Light calculateLights() { 194 int defaultLightColor = mContext.getResources().getColor( 195 com.android.internal.R.color.config_defaultNotificationColor); 196 int defaultLightOn = mContext.getResources().getInteger( 197 com.android.internal.R.integer.config_defaultNotificationLedOn); 198 int defaultLightOff = mContext.getResources().getInteger( 199 com.android.internal.R.integer.config_defaultNotificationLedOff); 200 201 int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor() 202 : defaultLightColor; 203 Light light = getChannel().shouldShowLights() ? new Light(channelLightColor, 204 defaultLightOn, defaultLightOff) : null; 205 if (mPreChannelsNotification 206 && (getChannel().getUserLockedFields() 207 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) { 208 final Notification notification = sbn.getNotification(); 209 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) { 210 light = new Light(notification.ledARGB, notification.ledOnMS, 211 notification.ledOffMS); 212 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) { 213 light = new Light(defaultLightColor, defaultLightOn, 214 defaultLightOff); 215 } 216 } else { 217 light = null; 218 } 219 } 220 return light; 221 } 222 calculateVibration()223 private long[] calculateVibration() { 224 long[] vibration; 225 final long[] defaultVibration = NotificationManagerService.getLongArray( 226 mContext.getResources(), 227 com.android.internal.R.array.config_defaultNotificationVibePattern, 228 NotificationManagerService.VIBRATE_PATTERN_MAXLEN, 229 NotificationManagerService.DEFAULT_VIBRATE_PATTERN); 230 if (getChannel().shouldVibrate()) { 231 vibration = getChannel().getVibrationPattern() == null 232 ? defaultVibration : getChannel().getVibrationPattern(); 233 } else { 234 vibration = null; 235 } 236 if (mPreChannelsNotification 237 && (getChannel().getUserLockedFields() 238 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) { 239 final Notification notification = sbn.getNotification(); 240 final boolean useDefaultVibrate = 241 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; 242 if (useDefaultVibrate) { 243 vibration = defaultVibration; 244 } else { 245 vibration = notification.vibrate; 246 } 247 } 248 return vibration; 249 } 250 calculateAttributes()251 private AudioAttributes calculateAttributes() { 252 final Notification n = sbn.getNotification(); 253 AudioAttributes attributes = getChannel().getAudioAttributes(); 254 if (attributes == null) { 255 attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; 256 } 257 258 if (mPreChannelsNotification 259 && (getChannel().getUserLockedFields() 260 & NotificationChannel.USER_LOCKED_SOUND) == 0) { 261 if (n.audioAttributes != null) { 262 // prefer audio attributes to stream type 263 attributes = n.audioAttributes; 264 } else if (n.audioStreamType >= 0 265 && n.audioStreamType < AudioSystem.getNumStreamTypes()) { 266 // the stream type is valid, use it 267 attributes = new AudioAttributes.Builder() 268 .setInternalLegacyStreamType(n.audioStreamType) 269 .build(); 270 } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) { 271 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType)); 272 } 273 } 274 return attributes; 275 } 276 calculateImportance()277 private int calculateImportance() { 278 final Notification n = sbn.getNotification(); 279 int importance = getChannel().getImportance(); 280 int requestedImportance = IMPORTANCE_DEFAULT; 281 282 // Migrate notification flags to scores 283 if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) { 284 n.priority = Notification.PRIORITY_MAX; 285 } 286 287 n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN, 288 Notification.PRIORITY_MAX); 289 switch (n.priority) { 290 case Notification.PRIORITY_MIN: 291 requestedImportance = IMPORTANCE_MIN; 292 break; 293 case Notification.PRIORITY_LOW: 294 requestedImportance = IMPORTANCE_LOW; 295 break; 296 case Notification.PRIORITY_DEFAULT: 297 requestedImportance = IMPORTANCE_DEFAULT; 298 break; 299 case Notification.PRIORITY_HIGH: 300 case Notification.PRIORITY_MAX: 301 requestedImportance = IMPORTANCE_HIGH; 302 break; 303 } 304 stats.requestedImportance = requestedImportance; 305 stats.isNoisy = mSound != null || mVibration != null; 306 307 if (mPreChannelsNotification 308 && (importance == IMPORTANCE_UNSPECIFIED 309 || (getChannel().getUserLockedFields() 310 & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0)) { 311 if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) { 312 requestedImportance = IMPORTANCE_LOW; 313 } 314 315 if (stats.isNoisy) { 316 if (requestedImportance < IMPORTANCE_DEFAULT) { 317 requestedImportance = IMPORTANCE_DEFAULT; 318 } 319 } 320 321 if (n.fullScreenIntent != null) { 322 requestedImportance = IMPORTANCE_HIGH; 323 } 324 importance = requestedImportance; 325 } 326 327 stats.naturalImportance = importance; 328 return importance; 329 } 330 331 // copy any notes that the ranking system may have made before the update copyRankingInformation(NotificationRecord previous)332 public void copyRankingInformation(NotificationRecord previous) { 333 mContactAffinity = previous.mContactAffinity; 334 mRecentlyIntrusive = previous.mRecentlyIntrusive; 335 mPackagePriority = previous.mPackagePriority; 336 mPackageVisibility = previous.mPackageVisibility; 337 mIntercept = previous.mIntercept; 338 mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs()); 339 mCreationTimeMs = previous.mCreationTimeMs; 340 mVisibleSinceMs = previous.mVisibleSinceMs; 341 if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) { 342 sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey()); 343 } 344 // Don't copy importance information or mGlobalSortKey, recompute them. 345 } 346 getNotification()347 public Notification getNotification() { return sbn.getNotification(); } getFlags()348 public int getFlags() { return sbn.getNotification().flags; } getUser()349 public UserHandle getUser() { return sbn.getUser(); } getKey()350 public String getKey() { return sbn.getKey(); } 351 /** @deprecated Use {@link #getUser()} instead. */ getUserId()352 public int getUserId() { return sbn.getUserId(); } 353 dump(ProtoOutputStream proto, boolean redact)354 void dump(ProtoOutputStream proto, boolean redact) { 355 proto.write(NotificationRecordProto.KEY, sbn.getKey()); 356 if (getChannel() != null) { 357 proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId()); 358 } 359 proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null); 360 proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null); 361 proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags); 362 proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey()); 363 proto.write(NotificationRecordProto.IMPORTANCE, getImportance()); 364 if (getSound() != null) { 365 proto.write(NotificationRecordProto.SOUND, getSound().toString()); 366 } 367 if (getAudioAttributes() != null) { 368 proto.write(NotificationRecordProto.SOUND_USAGE, getAudioAttributes().getUsage()); 369 } 370 } 371 formatRemoteViews(RemoteViews rv)372 String formatRemoteViews(RemoteViews rv) { 373 if (rv == null) return "null"; 374 return String.format("%s/0x%08x (%d bytes): %s", 375 rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString()); 376 } 377 dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)378 void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) { 379 final Notification notification = sbn.getNotification(); 380 final Icon icon = notification.getSmallIcon(); 381 String iconStr = String.valueOf(icon); 382 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 383 iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId()); 384 } 385 pw.println(prefix + this); 386 prefix = prefix + " "; 387 pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId()); 388 pw.println(prefix + "icon=" + iconStr); 389 pw.println(prefix + "pri=" + notification.priority); 390 pw.println(prefix + "key=" + sbn.getKey()); 391 pw.println(prefix + "seen=" + mIsSeen); 392 pw.println(prefix + "groupKey=" + getGroupKey()); 393 pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent); 394 pw.println(prefix + "contentIntent=" + notification.contentIntent); 395 pw.println(prefix + "deleteIntent=" + notification.deleteIntent); 396 397 pw.print(prefix + "tickerText="); 398 if (!TextUtils.isEmpty(notification.tickerText)) { 399 final String ticker = notification.tickerText.toString(); 400 if (redact) { 401 // if the string is long enough, we allow ourselves a few bytes for debugging 402 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : ""); 403 pw.println("..."); 404 } else { 405 pw.println(ticker); 406 } 407 } else { 408 pw.println("null"); 409 } 410 pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView)); 411 pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView)); 412 pw.println(prefix + "headsUpContentView=" 413 + formatRemoteViews(notification.headsUpContentView)); 414 pw.print(prefix + String.format("color=0x%08x", notification.color)); 415 pw.println(prefix + "timeout=" 416 + TimeUtils.formatForLogging(notification.getTimeoutAfter())); 417 if (notification.actions != null && notification.actions.length > 0) { 418 pw.println(prefix + "actions={"); 419 final int N = notification.actions.length; 420 for (int i = 0; i < N; i++) { 421 final Notification.Action action = notification.actions[i]; 422 if (action != null) { 423 pw.println(String.format("%s [%d] \"%s\" -> %s", 424 prefix, 425 i, 426 action.title, 427 action.actionIntent == null ? "null" : action.actionIntent.toString() 428 )); 429 } 430 } 431 pw.println(prefix + " }"); 432 } 433 if (notification.extras != null && notification.extras.size() > 0) { 434 pw.println(prefix + "extras={"); 435 for (String key : notification.extras.keySet()) { 436 pw.print(prefix + " " + key + "="); 437 Object val = notification.extras.get(key); 438 if (val == null) { 439 pw.println("null"); 440 } else { 441 pw.print(val.getClass().getSimpleName()); 442 if (redact && (val instanceof CharSequence || val instanceof String)) { 443 // redact contents from bugreports 444 } else if (val instanceof Bitmap) { 445 pw.print(String.format(" (%dx%d)", 446 ((Bitmap) val).getWidth(), 447 ((Bitmap) val).getHeight())); 448 } else if (val.getClass().isArray()) { 449 final int N = Array.getLength(val); 450 pw.print(" (" + N + ")"); 451 if (!redact) { 452 for (int j = 0; j < N; j++) { 453 pw.println(); 454 pw.print(String.format("%s [%d] %s", 455 prefix, j, String.valueOf(Array.get(val, j)))); 456 } 457 } 458 } else { 459 pw.print(" (" + String.valueOf(val) + ")"); 460 } 461 pw.println(); 462 } 463 } 464 pw.println(prefix + "}"); 465 } 466 pw.println(prefix + "stats=" + stats.toString()); 467 pw.println(prefix + "mContactAffinity=" + mContactAffinity); 468 pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive); 469 pw.println(prefix + "mPackagePriority=" + mPackagePriority); 470 pw.println(prefix + "mPackageVisibility=" + mPackageVisibility); 471 pw.println(prefix + "mUserImportance=" 472 + NotificationListenerService.Ranking.importanceToString(mUserImportance)); 473 pw.println(prefix + "mImportance=" 474 + NotificationListenerService.Ranking.importanceToString(mImportance)); 475 pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation); 476 pw.println(prefix + "mIntercept=" + mIntercept); 477 pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey); 478 pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs); 479 pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs); 480 pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs); 481 pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs); 482 pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects); 483 if (mPreChannelsNotification) { 484 pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x", 485 notification.defaults, notification.flags)); 486 pw.println(prefix + "n.sound=" + notification.sound); 487 pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType); 488 pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes); 489 pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", 490 notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); 491 pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate)); 492 } 493 pw.println(prefix + "mSound= " + mSound); 494 pw.println(prefix + "mVibration= " + mVibration); 495 pw.println(prefix + "mAttributes= " + mAttributes); 496 pw.println(prefix + "mLight= " + mLight); 497 pw.println(prefix + "mShowBadge=" + mShowBadge); 498 pw.println(prefix + "effectiveNotificationChannel=" + getChannel()); 499 if (getPeopleOverride() != null) { 500 pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride())); 501 } 502 if (getSnoozeCriteria() != null) { 503 pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria())); 504 } 505 } 506 507 idDebugString(Context baseContext, String packageName, int id)508 static String idDebugString(Context baseContext, String packageName, int id) { 509 Context c; 510 511 if (packageName != null) { 512 try { 513 c = baseContext.createPackageContext(packageName, 0); 514 } catch (NameNotFoundException e) { 515 c = baseContext; 516 } 517 } else { 518 c = baseContext; 519 } 520 521 Resources r = c.getResources(); 522 try { 523 return r.getResourceName(id); 524 } catch (Resources.NotFoundException e) { 525 return "<name unknown>"; 526 } 527 } 528 529 @Override toString()530 public final String toString() { 531 return String.format( 532 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" + 533 " channel=%s: %s)", 534 System.identityHashCode(this), 535 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(), 536 this.sbn.getTag(), this.mImportance, this.sbn.getKey(), this.getChannel().getId(), 537 this.sbn.getNotification()); 538 } 539 setContactAffinity(float contactAffinity)540 public void setContactAffinity(float contactAffinity) { 541 mContactAffinity = contactAffinity; 542 if (mImportance < IMPORTANCE_DEFAULT && 543 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) { 544 setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation()); 545 } 546 } 547 getContactAffinity()548 public float getContactAffinity() { 549 return mContactAffinity; 550 } 551 setRecentlyIntrusive(boolean recentlyIntrusive)552 public void setRecentlyIntrusive(boolean recentlyIntrusive) { 553 mRecentlyIntrusive = recentlyIntrusive; 554 if (recentlyIntrusive) { 555 mLastIntrusive = System.currentTimeMillis(); 556 } 557 } 558 isRecentlyIntrusive()559 public boolean isRecentlyIntrusive() { 560 return mRecentlyIntrusive; 561 } 562 getLastIntrusive()563 public long getLastIntrusive() { 564 return mLastIntrusive; 565 } 566 setPackagePriority(int packagePriority)567 public void setPackagePriority(int packagePriority) { 568 mPackagePriority = packagePriority; 569 } 570 getPackagePriority()571 public int getPackagePriority() { 572 return mPackagePriority; 573 } 574 setPackageVisibilityOverride(int packageVisibility)575 public void setPackageVisibilityOverride(int packageVisibility) { 576 mPackageVisibility = packageVisibility; 577 } 578 getPackageVisibilityOverride()579 public int getPackageVisibilityOverride() { 580 return mPackageVisibility; 581 } 582 setUserImportance(int importance)583 public void setUserImportance(int importance) { 584 mUserImportance = importance; 585 applyUserImportance(); 586 } 587 getUserExplanation()588 private String getUserExplanation() { 589 if (mUserExplanation == null) { 590 mUserExplanation = mContext.getResources().getString( 591 com.android.internal.R.string.importance_from_user); 592 } 593 return mUserExplanation; 594 } 595 getPeopleExplanation()596 private String getPeopleExplanation() { 597 if (mPeopleExplanation == null) { 598 mPeopleExplanation = mContext.getResources().getString( 599 com.android.internal.R.string.importance_from_person); 600 } 601 return mPeopleExplanation; 602 } 603 applyUserImportance()604 private void applyUserImportance() { 605 if (mUserImportance != IMPORTANCE_UNSPECIFIED) { 606 mImportance = mUserImportance; 607 mImportanceExplanation = getUserExplanation(); 608 } 609 } 610 getUserImportance()611 public int getUserImportance() { 612 return mUserImportance; 613 } 614 setImportance(int importance, CharSequence explanation)615 public void setImportance(int importance, CharSequence explanation) { 616 if (importance != IMPORTANCE_UNSPECIFIED) { 617 mImportance = importance; 618 mImportanceExplanation = explanation; 619 } 620 applyUserImportance(); 621 } 622 getImportance()623 public int getImportance() { 624 return mImportance; 625 } 626 getImportanceExplanation()627 public CharSequence getImportanceExplanation() { 628 return mImportanceExplanation; 629 } 630 setIntercepted(boolean intercept)631 public boolean setIntercepted(boolean intercept) { 632 mIntercept = intercept; 633 return mIntercept; 634 } 635 isIntercepted()636 public boolean isIntercepted() { 637 return mIntercept; 638 } 639 setSuppressedVisualEffects(int effects)640 public void setSuppressedVisualEffects(int effects) { 641 mSuppressedVisualEffects = effects; 642 } 643 getSuppressedVisualEffects()644 public int getSuppressedVisualEffects() { 645 return mSuppressedVisualEffects; 646 } 647 isCategory(String category)648 public boolean isCategory(String category) { 649 return Objects.equals(getNotification().category, category); 650 } 651 isAudioStream(int stream)652 public boolean isAudioStream(int stream) { 653 return getNotification().audioStreamType == stream; 654 } 655 isAudioAttributesUsage(int usage)656 public boolean isAudioAttributesUsage(int usage) { 657 final AudioAttributes attributes = getNotification().audioAttributes; 658 return attributes != null && attributes.getUsage() == usage; 659 } 660 661 /** 662 * Returns the timestamp to use for time-based sorting in the ranker. 663 */ getRankingTimeMs()664 public long getRankingTimeMs() { 665 return mRankingTimeMs; 666 } 667 668 /** 669 * @param now this current time in milliseconds. 670 * @returns the number of milliseconds since the most recent update, or the post time if none. 671 */ getFreshnessMs(long now)672 public int getFreshnessMs(long now) { 673 return (int) (now - mUpdateTimeMs); 674 } 675 676 /** 677 * @param now this current time in milliseconds. 678 * @returns the number of milliseconds since the the first post, ignoring updates. 679 */ getLifespanMs(long now)680 public int getLifespanMs(long now) { 681 return (int) (now - mCreationTimeMs); 682 } 683 684 /** 685 * @param now this current time in milliseconds. 686 * @returns the number of milliseconds since the most recent visibility event, or 0 if never. 687 */ getExposureMs(long now)688 public int getExposureMs(long now) { 689 return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs); 690 } 691 692 /** 693 * Set the visibility of the notification. 694 */ setVisibility(boolean visible, int rank)695 public void setVisibility(boolean visible, int rank) { 696 final long now = System.currentTimeMillis(); 697 mVisibleSinceMs = visible ? now : mVisibleSinceMs; 698 stats.onVisibilityChanged(visible); 699 MetricsLogger.action(getLogMaker(now) 700 .setCategory(MetricsEvent.NOTIFICATION_ITEM) 701 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE) 702 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank)); 703 if (visible) { 704 MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now)); 705 } 706 EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0, 707 getLifespanMs(now), 708 getFreshnessMs(now), 709 0, // exposure time 710 rank); 711 } 712 713 /** 714 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()} 715 * of the previous notification record, 0 otherwise 716 */ calculateRankingTimeMs(long previousRankingTimeMs)717 private long calculateRankingTimeMs(long previousRankingTimeMs) { 718 Notification n = getNotification(); 719 // Take developer provided 'when', unless it's in the future. 720 if (n.when != 0 && n.when <= sbn.getPostTime()) { 721 return n.when; 722 } 723 // If we've ranked a previous instance with a timestamp, inherit it. This case is 724 // important in order to have ranking stability for updating notifications. 725 if (previousRankingTimeMs > 0) { 726 return previousRankingTimeMs; 727 } 728 return sbn.getPostTime(); 729 } 730 setGlobalSortKey(String globalSortKey)731 public void setGlobalSortKey(String globalSortKey) { 732 mGlobalSortKey = globalSortKey; 733 } 734 getGlobalSortKey()735 public String getGlobalSortKey() { 736 return mGlobalSortKey; 737 } 738 739 /** Check if any of the listeners have marked this notification as seen by the user. */ isSeen()740 public boolean isSeen() { 741 return mIsSeen; 742 } 743 744 /** Mark the notification as seen by the user. */ setSeen()745 public void setSeen() { 746 mIsSeen = true; 747 } 748 setAuthoritativeRank(int authoritativeRank)749 public void setAuthoritativeRank(int authoritativeRank) { 750 mAuthoritativeRank = authoritativeRank; 751 } 752 getAuthoritativeRank()753 public int getAuthoritativeRank() { 754 return mAuthoritativeRank; 755 } 756 getGroupKey()757 public String getGroupKey() { 758 return sbn.getGroupKey(); 759 } 760 setOverrideGroupKey(String overrideGroupKey)761 public void setOverrideGroupKey(String overrideGroupKey) { 762 sbn.setOverrideGroupKey(overrideGroupKey); 763 mGroupLogTag = null; 764 } 765 getGroupLogTag()766 private String getGroupLogTag() { 767 if (mGroupLogTag == null) { 768 mGroupLogTag = shortenTag(sbn.getGroup()); 769 } 770 return mGroupLogTag; 771 } 772 getChannelIdLogTag()773 private String getChannelIdLogTag() { 774 if (mChannelIdLogTag == null) { 775 mChannelIdLogTag = shortenTag(mChannel.getId()); 776 } 777 return mChannelIdLogTag; 778 } 779 shortenTag(String longTag)780 private String shortenTag(String longTag) { 781 if (longTag == null) { 782 return null; 783 } 784 if (longTag.length() < MAX_LOGTAG_LENGTH) { 785 return longTag; 786 } else { 787 return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" + 788 Integer.toHexString(longTag.hashCode()); 789 } 790 } 791 isImportanceFromUser()792 public boolean isImportanceFromUser() { 793 return mImportance == mUserImportance; 794 } 795 getChannel()796 public NotificationChannel getChannel() { 797 return mChannel; 798 } 799 updateNotificationChannel(NotificationChannel channel)800 protected void updateNotificationChannel(NotificationChannel channel) { 801 if (channel != null) { 802 mChannel = channel; 803 calculateImportance(); 804 } 805 } 806 setShowBadge(boolean showBadge)807 public void setShowBadge(boolean showBadge) { 808 mShowBadge = showBadge; 809 } 810 canShowBadge()811 public boolean canShowBadge() { 812 return mShowBadge; 813 } 814 getLight()815 public Light getLight() { 816 return mLight; 817 } 818 getSound()819 public Uri getSound() { 820 return mSound; 821 } 822 getVibration()823 public long[] getVibration() { 824 return mVibration; 825 } 826 getAudioAttributes()827 public AudioAttributes getAudioAttributes() { 828 return mAttributes; 829 } 830 getPeopleOverride()831 public ArrayList<String> getPeopleOverride() { 832 return mPeopleOverride; 833 } 834 setPeopleOverride(ArrayList<String> people)835 protected void setPeopleOverride(ArrayList<String> people) { 836 mPeopleOverride = people; 837 } 838 getSnoozeCriteria()839 public ArrayList<SnoozeCriterion> getSnoozeCriteria() { 840 return mSnoozeCriteria; 841 } 842 setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria)843 protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) { 844 mSnoozeCriteria = snoozeCriteria; 845 } 846 getLogMaker(long now)847 public LogMaker getLogMaker(long now) { 848 if (mLogMaker == null) { 849 // initialize fields that only change on update (so a new record) 850 mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN) 851 .setPackageName(sbn.getPackageName()) 852 .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId()) 853 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag()) 854 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag()); 855 } 856 // reset fields that can change between updates, or are used by multiple logs 857 return mLogMaker 858 .clearCategory() 859 .clearType() 860 .clearSubtype() 861 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX) 862 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance) 863 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag()) 864 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY, 865 sbn.getNotification().isGroupSummary() ? 1 : 0) 866 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now)) 867 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now)) 868 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now)); 869 } 870 getLogMaker()871 public LogMaker getLogMaker() { 872 return getLogMaker(System.currentTimeMillis()); 873 } 874 875 @VisibleForTesting 876 static final class Light { 877 public final int color; 878 public final int onMs; 879 public final int offMs; 880 Light(int color, int onMs, int offMs)881 public Light(int color, int onMs, int offMs) { 882 this.color = color; 883 this.onMs = onMs; 884 this.offMs = offMs; 885 } 886 887 @Override equals(Object o)888 public boolean equals(Object o) { 889 if (this == o) return true; 890 if (o == null || getClass() != o.getClass()) return false; 891 892 Light light = (Light) o; 893 894 if (color != light.color) return false; 895 if (onMs != light.onMs) return false; 896 return offMs == light.offMs; 897 898 } 899 900 @Override hashCode()901 public int hashCode() { 902 int result = color; 903 result = 31 * result + onMs; 904 result = 31 * result + offMs; 905 return result; 906 } 907 908 @Override toString()909 public String toString() { 910 return "Light{" + 911 "color=" + color + 912 ", onMs=" + onMs + 913 ", offMs=" + offMs + 914 '}'; 915 } 916 } 917 } 918