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