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