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