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