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