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