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