2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.server.notification;
18 import static android.app.Notification.EXTRA_AUDIO_CONTENTS_URI;
19 import static android.app.Notification.EXTRA_BACKGROUND_IMAGE_URI;
20 import static android.app.Notification.EXTRA_HISTORIC_MESSAGES;
21 import static android.app.Notification.EXTRA_MESSAGES;
22 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
23 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
24 import static android.app.NotificationManager.IMPORTANCE_HIGH;
25 import static android.app.NotificationManager.IMPORTANCE_LOW;
26 import static android.app.NotificationManager.IMPORTANCE_MIN;
27 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
28 import static android.service.notification.NotificationListenerService.Ranking
29 .USER_SENTIMENT_NEUTRAL;
30 import static android.service.notification.NotificationListenerService.Ranking
31 .USER_SENTIMENT_POSITIVE;
33 import android.annotation.Nullable;
34 import android.app.ActivityManager;
35 import android.app.ActivityManagerInternal;
36 import android.app.IActivityManager;
37 import android.app.Notification;
38 import android.app.Notification.MessagingStyle;
39 import android.app.NotificationChannel;
40 import android.content.ContentProvider;
41 import android.content.ContentResolver;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.pm.PackageManager;
45 import android.content.pm.PackageManager.NameNotFoundException;
46 import android.content.pm.PackageManagerInternal;
47 import android.content.res.Resources;
48 import android.graphics.Bitmap;
49 import android.graphics.drawable.Icon;
50 import android.media.AudioAttributes;
51 import android.media.AudioSystem;
52 import android.metrics.LogMaker;
53 import android.net.Uri;
54 import android.os.Binder;
55 import android.os.Build;
56 import android.os.Bundle;
57 import android.os.IBinder;
58 import android.os.Parcelable;
59 import android.os.RemoteException;
60 import android.os.UserHandle;
61 import android.provider.Settings;
62 import android.service.notification.Adjustment;
63 import android.service.notification.NotificationListenerService;
64 import android.service.notification.NotificationRecordProto;
65 import android.service.notification.NotificationStats;
66 import android.service.notification.SnoozeCriterion;
67 import android.service.notification.StatusBarNotification;
68 import android.text.TextUtils;
69 import android.util.ArraySet;
70 import android.util.Log;
71 import android.util.TimeUtils;
72 import android.util.proto.ProtoOutputStream;
73 import android.widget.RemoteViews;
75 import com.android.internal.annotations.VisibleForTesting;
76 import com.android.internal.logging.MetricsLogger;
77 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
78 import com.android.internal.util.ArrayUtils;
79 import com.android.server.EventLogTags;
80 import com.android.server.LocalServices;
82 import java.io.PrintWriter;
83 import java.lang.reflect.Array;
84 import java.util.ArrayList;
85 import java.util.Arrays;
86 import java.util.List;
87 import java.util.Objects;
90 * Holds data about notifications that should not be shared with the
91 * {@link android.service.notification.NotificationListenerService}s.
93 * <p>These objects should not be mutated unless the code is synchronized
94 * on {@link NotificationManagerService#mNotificationLock}, and any
95 * modification should be followed by a sorting of that list.</p>
97 * <p>Is sortable by {@link NotificationComparator}.</p>
101 public final class NotificationRecord {
102 static final String TAG = "NotificationRecord";
103 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
104 private static final int MAX_LOGTAG_LENGTH = 35;
105 final StatusBarNotification sbn;
106 final int mTargetSdkVersion;
107 final int mOriginalFlags;
108 private final Context mContext;
110 NotificationUsageStats.SingleNotificationStats stats;
112 IBinder permissionOwner;
114 // These members are used by NotificationSignalExtractors
115 // to communicate with the ranking module.
116 private float mContactAffinity;
117 private boolean mRecentlyIntrusive;
118 private long mLastIntrusive;
120 // is this notification currently being intercepted by Zen Mode?
121 private boolean mIntercept;
123 // is this notification hidden since the app pkg is suspended?
124 private boolean mHidden;
126 // The timestamp used for ranking.
127 private long mRankingTimeMs;
129 // The first post time, stable across updates.
130 private long mCreationTimeMs;
132 // The most recent visibility event.
133 private long mVisibleSinceMs;
135 // The most recent update time, or the creation time if no updates.
136 private long mUpdateTimeMs;
138 // Is this record an update of an old record?
139 public boolean isUpdate;
140 private int mPackagePriority;
142 private int mAuthoritativeRank;
143 private String mGlobalSortKey;
144 private int mPackageVisibility;
145 private int mUserImportance = IMPORTANCE_UNSPECIFIED;
146 private int mImportance = IMPORTANCE_UNSPECIFIED;
147 private CharSequence mImportanceExplanation = null;
149 private int mSuppressedVisualEffects = 0;
150 private String mUserExplanation;
151 private String mPeopleExplanation;
152 private boolean mPreChannelsNotification = true;
154 private long[] mVibration;
155 private AudioAttributes mAttributes;
156 private NotificationChannel mChannel;
157 private ArrayList<String> mPeopleOverride;
158 private ArrayList<SnoozeCriterion> mSnoozeCriteria;
159 private boolean mShowBadge;
160 private LogMaker mLogMaker;
161 private Light mLight;
162 private String mGroupLogTag;
163 private String mChannelIdLogTag;
165 private final List<Adjustment> mAdjustments;
166 private final NotificationStats mStats;
167 private int mUserSentiment;
168 private boolean mIsInterruptive;
169 private int mNumberOfSmartRepliesAdded;
170 private boolean mHasSeenSmartReplies;
172 * Whether this notification (and its channels) should be considered user locked. Used in
173 * conjunction with user sentiment calculation.
175 private boolean mIsAppImportanceLocked;
176 private ArraySet<Uri> mGrantableUris;
178 public NotificationRecord(Context context, StatusBarNotification sbn,
179 NotificationChannel channel) {
181 mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
182 .getPackageTargetSdkVersion(sbn.getPackageName());
183 mOriginalFlags = sbn.getNotification().flags;
184 mRankingTimeMs = calculateRankingTimeMs(0L);
185 mCreationTimeMs = sbn.getPostTime();
186 mUpdateTimeMs = mCreationTimeMs;
188 stats = new NotificationUsageStats.SingleNotificationStats();
190 mPreChannelsNotification = isPreChannelsNotification();
191 mSound = calculateSound();
192 mVibration = calculateVibration();
193 mAttributes = calculateAttributes();
194 mImportance = calculateImportance();
195 mLight = calculateLights();
196 mAdjustments = new ArrayList<>();
197 mStats = new NotificationStats();
198 calculateUserSentiment();
199 calculateGrantableUris();
202 private boolean isPreChannelsNotification() {
203 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) {
204 if (mTargetSdkVersion < Build.VERSION_CODES.O) {
211 private Uri calculateSound() {
212 final Notification n = sbn.getNotification();
214 // No notification sounds on tv
215 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
219 Uri sound = mChannel.getSound();
220 if (mPreChannelsNotification && (getChannel().getUserLockedFields()
221 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
223 final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0;
224 if (useDefaultSound) {
225 sound = Settings.System.DEFAULT_NOTIFICATION_URI;
233 private Light calculateLights() {
234 int defaultLightColor = mContext.getResources().getColor(
235 com.android.internal.R.color.config_defaultNotificationColor);
236 int defaultLightOn = mContext.getResources().getInteger(
237 com.android.internal.R.integer.config_defaultNotificationLedOn);
238 int defaultLightOff = mContext.getResources().getInteger(
239 com.android.internal.R.integer.config_defaultNotificationLedOff);
241 int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor()
243 Light light = getChannel().shouldShowLights() ? new Light(channelLightColor,
244 defaultLightOn, defaultLightOff) : null;
245 if (mPreChannelsNotification
246 && (getChannel().getUserLockedFields()
247 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
248 final Notification notification = sbn.getNotification();
249 if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
250 light = new Light(notification.ledARGB, notification.ledOnMS,
251 notification.ledOffMS);
252 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
253 light = new Light(defaultLightColor, defaultLightOn,
263 private long[] calculateVibration() {
265 final long[] defaultVibration = NotificationManagerService.getLongArray(
266 mContext.getResources(),
267 com.android.internal.R.array.config_defaultNotificationVibePattern,
268 NotificationManagerService.VIBRATE_PATTERN_MAXLEN,
269 NotificationManagerService.DEFAULT_VIBRATE_PATTERN);
270 if (getChannel().shouldVibrate()) {
271 vibration = getChannel().getVibrationPattern() == null
272 ? defaultVibration : getChannel().getVibrationPattern();
276 if (mPreChannelsNotification
277 && (getChannel().getUserLockedFields()
278 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
279 final Notification notification = sbn.getNotification();
280 final boolean useDefaultVibrate =
281 (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
282 if (useDefaultVibrate) {
283 vibration = defaultVibration;
285 vibration = notification.vibrate;
291 private AudioAttributes calculateAttributes() {
292 final Notification n = sbn.getNotification();
293 AudioAttributes attributes = getChannel().getAudioAttributes();
294 if (attributes == null) {
295 attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
298 if (mPreChannelsNotification
299 && (getChannel().getUserLockedFields()
300 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
301 if (n.audioAttributes != null) {
302 // prefer audio attributes to stream type
303 attributes = n.audioAttributes;
304 } else if (n.audioStreamType >= 0
305 && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
306 // the stream type is valid, use it
307 attributes = new AudioAttributes.Builder()
308 .setInternalLegacyStreamType(n.audioStreamType)
310 } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
311 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
317 private int calculateImportance() {
318 final Notification n = sbn.getNotification();
319 int importance = getChannel().getImportance();
320 int requestedImportance = IMPORTANCE_DEFAULT;
322 // Migrate notification flags to scores
323 if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
324 n.priority = Notification.PRIORITY_MAX;
327 n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN,
328 Notification.PRIORITY_MAX);
329 switch (n.priority) {
330 case Notification.PRIORITY_MIN:
331 requestedImportance = IMPORTANCE_MIN;
333 case Notification.PRIORITY_LOW:
334 requestedImportance = IMPORTANCE_LOW;
336 case Notification.PRIORITY_DEFAULT:
337 requestedImportance = IMPORTANCE_DEFAULT;
339 case Notification.PRIORITY_HIGH:
340 case Notification.PRIORITY_MAX:
341 requestedImportance = IMPORTANCE_HIGH;
344 stats.requestedImportance = requestedImportance;
345 stats.isNoisy = mSound != null || mVibration != null;
347 if (mPreChannelsNotification
348 && (importance == IMPORTANCE_UNSPECIFIED
349 || (getChannel().getUserLockedFields()
350 & USER_LOCKED_IMPORTANCE) == 0)) {
351 if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
352 requestedImportance = IMPORTANCE_LOW;
356 if (requestedImportance < IMPORTANCE_DEFAULT) {
357 requestedImportance = IMPORTANCE_DEFAULT;
361 if (n.fullScreenIntent != null) {
362 requestedImportance = IMPORTANCE_HIGH;
364 importance = requestedImportance;
367 stats.naturalImportance = importance;
371 // copy any notes that the ranking system may have made before the update
372 public void copyRankingInformation(NotificationRecord previous) {
373 mContactAffinity = previous.mContactAffinity;
374 mRecentlyIntrusive = previous.mRecentlyIntrusive;
375 mPackagePriority = previous.mPackagePriority;
376 mPackageVisibility = previous.mPackageVisibility;
377 mIntercept = previous.mIntercept;
378 mHidden = previous.mHidden;
379 mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
380 mCreationTimeMs = previous.mCreationTimeMs;
381 mVisibleSinceMs = previous.mVisibleSinceMs;
382 if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
383 sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
385 // Don't copy importance information or mGlobalSortKey, recompute them.
388 public Notification getNotification() { return sbn.getNotification(); }
389 public int getFlags() { return sbn.getNotification().flags; }
390 public UserHandle getUser() { return sbn.getUser(); }
391 public String getKey() { return sbn.getKey(); }
392 /** @deprecated Use {@link #getUser()} instead. */
393 public int getUserId() { return sbn.getUserId(); }
394 public int getUid() { return sbn.getUid(); }
396 void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) {
397 final long token = proto.start(fieldId);
399 proto.write(NotificationRecordProto.KEY, sbn.getKey());
400 proto.write(NotificationRecordProto.STATE, state);
401 if (getChannel() != null) {
402 proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
404 proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null);
405 proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null);
406 proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags);
407 proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey());
408 proto.write(NotificationRecordProto.IMPORTANCE, getImportance());
409 if (getSound() != null) {
410 proto.write(NotificationRecordProto.SOUND, getSound().toString());
412 if (getAudioAttributes() != null) {
413 getAudioAttributes().writeToProto(proto, NotificationRecordProto.AUDIO_ATTRIBUTES);
419 String formatRemoteViews(RemoteViews rv) {
420 if (rv == null) return "null";
421 return String.format("%s/0x%08x (%d bytes): %s",
422 rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString());
425 void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
426 final Notification notification = sbn.getNotification();
427 final Icon icon = notification.getSmallIcon();
428 String iconStr = String.valueOf(icon);
429 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
430 iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
432 pw.println(prefix + this);
433 prefix = prefix + " ";
434 pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
435 pw.println(prefix + "icon=" + iconStr);
436 pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
437 pw.println(prefix + "pri=" + notification.priority);
438 pw.println(prefix + "key=" + sbn.getKey());
439 pw.println(prefix + "seen=" + mStats.hasSeen());
440 pw.println(prefix + "groupKey=" + getGroupKey());
441 pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
442 pw.println(prefix + "contentIntent=" + notification.contentIntent);
443 pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
445 pw.print(prefix + "tickerText=");
446 if (!TextUtils.isEmpty(notification.tickerText)) {
447 final String ticker = notification.tickerText.toString();
449 // if the string is long enough, we allow ourselves a few bytes for debugging
450 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : "");
458 pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
459 pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
460 pw.println(prefix + "headsUpContentView="
461 + formatRemoteViews(notification.headsUpContentView));
462 pw.print(prefix + String.format("color=0x%08x", notification.color));
463 pw.println(prefix + "timeout="
464 + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
465 if (notification.actions != null && notification.actions.length > 0) {
466 pw.println(prefix + "actions={");
467 final int N = notification.actions.length;
468 for (int i = 0; i < N; i++) {
469 final Notification.Action action = notification.actions[i];
470 if (action != null) {
471 pw.println(String.format("%s [%d] \"%s\" -> %s",
475 action.actionIntent == null ? "null" : action.actionIntent.toString()
479 pw.println(prefix + " }");
481 if (notification.extras != null && notification.extras.size() > 0) {
482 pw.println(prefix + "extras={");
483 for (String key : notification.extras.keySet()) {
484 pw.print(prefix + " " + key + "=");
485 Object val = notification.extras.get(key);
489 pw.print(val.getClass().getSimpleName());
490 if (redact && (val instanceof CharSequence || val instanceof String)) {
491 // redact contents from bugreports
492 } else if (val instanceof Bitmap) {
493 pw.print(String.format(" (%dx%d)",
494 ((Bitmap) val).getWidth(),
495 ((Bitmap) val).getHeight()));
496 } else if (val.getClass().isArray()) {
497 final int N = Array.getLength(val);
498 pw.print(" (" + N + ")");
500 for (int j = 0; j < N; j++) {
502 pw.print(String.format("%s [%d] %s",
503 prefix, j, String.valueOf(Array.get(val, j))));
507 pw.print(" (" + String.valueOf(val) + ")");
512 pw.println(prefix + "}");
514 pw.println(prefix + "stats=" + stats.toString());
515 pw.println(prefix + "mContactAffinity=" + mContactAffinity);
516 pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
517 pw.println(prefix + "mPackagePriority=" + mPackagePriority);
518 pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
519 pw.println(prefix + "mUserImportance="
520 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
521 pw.println(prefix + "mImportance="
522 + NotificationListenerService.Ranking.importanceToString(mImportance));
523 pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
524 pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
525 pw.println(prefix + "mIntercept=" + mIntercept);
526 pw.println(prefix + "mHidden==" + mHidden);
527 pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
528 pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
529 pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
530 pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
531 pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
532 pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
533 if (mPreChannelsNotification) {
534 pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
535 notification.defaults, notification.flags));
536 pw.println(prefix + "n.sound=" + notification.sound);
537 pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
538 pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
539 pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
540 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
541 pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
543 pw.println(prefix + "mSound= " + mSound);
544 pw.println(prefix + "mVibration= " + mVibration);
545 pw.println(prefix + "mAttributes= " + mAttributes);
546 pw.println(prefix + "mLight= " + mLight);
547 pw.println(prefix + "mShowBadge=" + mShowBadge);
548 pw.println(prefix + "mColorized=" + notification.isColorized());
549 pw.println(prefix + "mIsInterruptive=" + mIsInterruptive);
550 pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
551 if (getPeopleOverride() != null) {
552 pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
554 if (getSnoozeCriteria() != null) {
555 pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
557 pw.println(prefix + "mAdjustments=" + mAdjustments);
561 static String idDebugString(Context baseContext, String packageName, int id) {
564 if (packageName != null) {
566 c = baseContext.createPackageContext(packageName, 0);
567 } catch (NameNotFoundException e) {
574 Resources r = c.getResources();
576 return r.getResourceName(id);
577 } catch (Resources.NotFoundException e) {
578 return "<name unknown>";
583 public final String toString() {
584 return String.format(
585 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
586 "appImportanceLocked=%s: %s)",
587 System.identityHashCode(this),
588 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
589 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
590 mIsAppImportanceLocked, this.sbn.getNotification());
593 public void addAdjustment(Adjustment adjustment) {
594 synchronized (mAdjustments) {
595 mAdjustments.add(adjustment);
599 public void applyAdjustments() {
600 synchronized (mAdjustments) {
601 for (Adjustment adjustment: mAdjustments) {
602 Bundle signals = adjustment.getSignals();
603 if (signals.containsKey(Adjustment.KEY_PEOPLE)) {
604 final ArrayList<String> people =
605 adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
606 setPeopleOverride(people);
608 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
609 final ArrayList<SnoozeCriterion> snoozeCriterionList =
610 adjustment.getSignals().getParcelableArrayList(
611 Adjustment.KEY_SNOOZE_CRITERIA);
612 setSnoozeCriteria(snoozeCriterionList);
614 if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
615 final String groupOverrideKey =
616 adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
617 setOverrideGroupKey(groupOverrideKey);
619 if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
620 // Only allow user sentiment update from assistant if user hasn't already
621 // expressed a preference for this channel
622 if (!mIsAppImportanceLocked
623 && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
624 setUserSentiment(adjustment.getSignals().getInt(
625 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
632 public void setIsAppImportanceLocked(boolean isAppImportanceLocked) {
633 mIsAppImportanceLocked = isAppImportanceLocked;
634 calculateUserSentiment();
637 public void setContactAffinity(float contactAffinity) {
638 mContactAffinity = contactAffinity;
639 if (mImportance < IMPORTANCE_DEFAULT &&
640 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
641 setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
645 public float getContactAffinity() {
646 return mContactAffinity;
649 public void setRecentlyIntrusive(boolean recentlyIntrusive) {
650 mRecentlyIntrusive = recentlyIntrusive;
651 if (recentlyIntrusive) {
652 mLastIntrusive = System.currentTimeMillis();
656 public boolean isRecentlyIntrusive() {
657 return mRecentlyIntrusive;
660 public long getLastIntrusive() {
661 return mLastIntrusive;
664 public void setPackagePriority(int packagePriority) {
665 mPackagePriority = packagePriority;
668 public int getPackagePriority() {
669 return mPackagePriority;
672 public void setPackageVisibilityOverride(int packageVisibility) {
673 mPackageVisibility = packageVisibility;
676 public int getPackageVisibilityOverride() {
677 return mPackageVisibility;
680 public void setUserImportance(int importance) {
681 mUserImportance = importance;
682 applyUserImportance();
685 private String getUserExplanation() {
686 if (mUserExplanation == null) {
687 mUserExplanation = mContext.getResources().getString(
688 com.android.internal.R.string.importance_from_user);
690 return mUserExplanation;
693 private String getPeopleExplanation() {
694 if (mPeopleExplanation == null) {
695 mPeopleExplanation = mContext.getResources().getString(
696 com.android.internal.R.string.importance_from_person);
698 return mPeopleExplanation;
701 private void applyUserImportance() {
702 if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
703 mImportance = mUserImportance;
704 mImportanceExplanation = getUserExplanation();
708 public int getUserImportance() {
709 return mUserImportance;
712 public void setImportance(int importance, CharSequence explanation) {
713 if (importance != IMPORTANCE_UNSPECIFIED) {
714 mImportance = importance;
715 mImportanceExplanation = explanation;
717 applyUserImportance();
720 public int getImportance() {
724 public CharSequence getImportanceExplanation() {
725 return mImportanceExplanation;
728 public boolean setIntercepted(boolean intercept) {
729 mIntercept = intercept;
733 public boolean isIntercepted() {
737 public void setHidden(boolean hidden) {
741 public boolean isHidden() {
746 public void setSuppressedVisualEffects(int effects) {
747 mSuppressedVisualEffects = effects;
750 public int getSuppressedVisualEffects() {
751 return mSuppressedVisualEffects;
754 public boolean isCategory(String category) {
755 return Objects.equals(getNotification().category, category);
758 public boolean isAudioAttributesUsage(int usage) {
759 return mAttributes != null && mAttributes.getUsage() == usage;
763 * Returns the timestamp to use for time-based sorting in the ranker.
765 public long getRankingTimeMs() {
766 return mRankingTimeMs;
770 * @param now this current time in milliseconds.
771 * @returns the number of milliseconds since the most recent update, or the post time if none.
773 public int getFreshnessMs(long now) {
774 return (int) (now - mUpdateTimeMs);
778 * @param now this current time in milliseconds.
779 * @returns the number of milliseconds since the the first post, ignoring updates.
781 public int getLifespanMs(long now) {
782 return (int) (now - mCreationTimeMs);
786 * @param now this current time in milliseconds.
787 * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
789 public int getExposureMs(long now) {
790 return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
794 * Set the visibility of the notification.
796 public void setVisibility(boolean visible, int rank, int count) {
797 final long now = System.currentTimeMillis();
798 mVisibleSinceMs = visible ? now : mVisibleSinceMs;
799 stats.onVisibilityChanged(visible);
800 MetricsLogger.action(getLogMaker(now)
801 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
802 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
803 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank)
804 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count));
807 MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
809 EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
817 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
818 * of the previous notification record, 0 otherwise
820 private long calculateRankingTimeMs(long previousRankingTimeMs) {
821 Notification n = getNotification();
822 // Take developer provided 'when', unless it's in the future.
823 if (n.when != 0 && n.when <= sbn.getPostTime()) {
826 // If we've ranked a previous instance with a timestamp, inherit it. This case is
827 // important in order to have ranking stability for updating notifications.
828 if (previousRankingTimeMs > 0) {
829 return previousRankingTimeMs;
831 return sbn.getPostTime();
834 public void setGlobalSortKey(String globalSortKey) {
835 mGlobalSortKey = globalSortKey;
838 public String getGlobalSortKey() {
839 return mGlobalSortKey;
842 /** Check if any of the listeners have marked this notification as seen by the user. */
843 public boolean isSeen() {
844 return mStats.hasSeen();
847 /** Mark the notification as seen by the user. */
848 public void setSeen() {
852 public void setAuthoritativeRank(int authoritativeRank) {
853 mAuthoritativeRank = authoritativeRank;
856 public int getAuthoritativeRank() {
857 return mAuthoritativeRank;
860 public String getGroupKey() {
861 return sbn.getGroupKey();
864 public void setOverrideGroupKey(String overrideGroupKey) {
865 sbn.setOverrideGroupKey(overrideGroupKey);
869 private String getGroupLogTag() {
870 if (mGroupLogTag == null) {
871 mGroupLogTag = shortenTag(sbn.getGroup());
876 private String getChannelIdLogTag() {
877 if (mChannelIdLogTag == null) {
878 mChannelIdLogTag = shortenTag(mChannel.getId());
880 return mChannelIdLogTag;
883 private String shortenTag(String longTag) {
884 if (longTag == null) {
887 if (longTag.length() < MAX_LOGTAG_LENGTH) {
890 return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
891 Integer.toHexString(longTag.hashCode());
895 public NotificationChannel getChannel() {
900 * @see RankingHelper#getIsAppImportanceLocked(String, int)
902 public boolean getIsAppImportanceLocked() {
903 return mIsAppImportanceLocked;
906 protected void updateNotificationChannel(NotificationChannel channel) {
907 if (channel != null) {
909 calculateImportance();
910 calculateUserSentiment();
914 public void setShowBadge(boolean showBadge) {
915 mShowBadge = showBadge;
918 public boolean canShowBadge() {
922 public Light getLight() {
926 public Uri getSound() {
930 public long[] getVibration() {
934 public AudioAttributes getAudioAttributes() {
938 public ArrayList<String> getPeopleOverride() {
939 return mPeopleOverride;
942 public void setInterruptive(boolean interruptive) {
943 mIsInterruptive = interruptive;
946 public boolean isInterruptive() {
947 return mIsInterruptive;
950 protected void setPeopleOverride(ArrayList<String> people) {
951 mPeopleOverride = people;
954 public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
955 return mSnoozeCriteria;
958 protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
959 mSnoozeCriteria = snoozeCriteria;
962 private void calculateUserSentiment() {
963 if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0
964 || mIsAppImportanceLocked) {
965 mUserSentiment = USER_SENTIMENT_POSITIVE;
969 private void setUserSentiment(int userSentiment) {
970 mUserSentiment = userSentiment;
973 public int getUserSentiment() {
974 return mUserSentiment;
977 public NotificationStats getStats() {
981 public void recordExpanded() {
982 mStats.setExpanded();
985 public void recordDirectReplied() {
986 mStats.setDirectReplied();
989 public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
990 mStats.setDismissalSurface(surface);
993 public void recordSnoozed() {
997 public void recordViewedSettings() {
998 mStats.setViewedSettings();
1001 public void setNumSmartRepliesAdded(int noReplies) {
1002 mNumberOfSmartRepliesAdded = noReplies;
1005 public int getNumSmartRepliesAdded() {
1006 return mNumberOfSmartRepliesAdded;
1009 public boolean hasSeenSmartReplies() {
1010 return mHasSeenSmartReplies;
1013 public void setSeenSmartReplies(boolean hasSeenSmartReplies) {
1014 mHasSeenSmartReplies = hasSeenSmartReplies;
1018 * @return all {@link Uri} that should have permission granted to whoever
1019 * will be rendering it. This list has already been vetted to only
1020 * include {@link Uri} that the enqueuing app can grant.
1022 public @Nullable ArraySet<Uri> getGrantableUris() {
1023 return mGrantableUris;
1027 * Collect all {@link Uri} that should have permission granted to whoever
1028 * will be rendering it.
1030 private void calculateGrantableUris() {
1031 final Notification notification = getNotification();
1033 noteGrantableUri(notification.sound);
1034 if (notification.getChannelId() != null) {
1035 NotificationChannel channel = getChannel();
1036 if (channel != null) {
1037 noteGrantableUri(channel.getSound());
1041 final Bundle extras = notification.extras;
1042 if (extras != null) {
1043 noteGrantableUri(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI));
1044 noteGrantableUri(extras.getParcelable(EXTRA_BACKGROUND_IMAGE_URI));
1047 if (MessagingStyle.class.equals(notification.getNotificationStyle()) && extras != null) {
1048 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
1049 if (!ArrayUtils.isEmpty(messages)) {
1050 for (MessagingStyle.Message message : MessagingStyle.Message
1051 .getMessagesFromBundleArray(messages)) {
1052 noteGrantableUri(message.getDataUri());
1056 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
1057 if (!ArrayUtils.isEmpty(historic)) {
1058 for (MessagingStyle.Message message : MessagingStyle.Message
1059 .getMessagesFromBundleArray(historic)) {
1060 noteGrantableUri(message.getDataUri());
1067 * Note the presence of a {@link Uri} that should have permission granted to
1068 * whoever will be rendering it.
1070 * If the enqueuing app has the ability to grant access, it will be added to
1071 * {@link #mGrantableUris}. Otherwise, this will either log or throw
1072 * {@link SecurityException} depending on target SDK of enqueuing app.
1074 private void noteGrantableUri(Uri uri) {
1075 if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1077 // We can't grant Uri permissions from system
1078 final int sourceUid = sbn.getUid();
1079 if (sourceUid == android.os.Process.SYSTEM_UID) return;
1081 final IActivityManager am = ActivityManager.getService();
1082 final long ident = Binder.clearCallingIdentity();
1084 // This will throw SecurityException if caller can't grant
1085 am.checkGrantUriPermission(sourceUid, null,
1086 ContentProvider.getUriWithoutUserId(uri),
1087 Intent.FLAG_GRANT_READ_URI_PERMISSION,
1088 ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
1090 if (mGrantableUris == null) {
1091 mGrantableUris = new ArraySet<>();
1093 mGrantableUris.add(uri);
1094 } catch (RemoteException ignored) {
1095 // Ignored because we're in same process
1096 } catch (SecurityException e) {
1097 if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
1100 Log.w(TAG, "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
1103 Binder.restoreCallingIdentity(ident);
1107 public LogMaker getLogMaker(long now) {
1108 if (mLogMaker == null) {
1109 // initialize fields that only change on update (so a new record)
1110 mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
1111 .setPackageName(sbn.getPackageName())
1112 .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
1113 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
1114 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
1116 // reset fields that can change between updates, or are used by multiple logs
1121 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
1122 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
1123 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
1124 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
1125 sbn.getNotification().isGroupSummary() ? 1 : 0)
1126 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
1127 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
1128 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
1131 public LogMaker getLogMaker() {
1132 return getLogMaker(System.currentTimeMillis());
1136 static final class Light {
1137 public final int color;
1138 public final int onMs;
1139 public final int offMs;
1141 public Light(int color, int onMs, int offMs) {
1148 public boolean equals(Object o) {
1149 if (this == o) return true;
1150 if (o == null || getClass() != o.getClass()) return false;
1152 Light light = (Light) o;
1154 if (color != light.color) return false;
1155 if (onMs != light.onMs) return false;
1156 return offMs == light.offMs;
1161 public int hashCode() {
1163 result = 31 * result + onMs;
1164 result = 31 * result + offMs;
1169 public String toString() {
1173 ", offMs=" + offMs +