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