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
25         .USER_SENTIMENT_NEUTRAL;
26 import static android.service.notification.NotificationListenerService.Ranking
27         .USER_SENTIMENT_POSITIVE;
28 
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.IActivityManager;
32 import android.app.Notification;
33 import android.app.NotificationChannel;
34 import android.content.ContentProvider;
35 import android.content.ContentResolver;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.PackageManager;
39 import android.content.pm.PackageManager.NameNotFoundException;
40 import android.content.pm.PackageManagerInternal;
41 import android.content.res.Resources;
42 import android.graphics.Bitmap;
43 import android.graphics.drawable.Icon;
44 import android.media.AudioAttributes;
45 import android.media.AudioSystem;
46 import android.metrics.LogMaker;
47 import android.net.Uri;
48 import android.os.Binder;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.IBinder;
52 import android.os.RemoteException;
53 import android.os.UserHandle;
54 import android.provider.Settings;
55 import android.service.notification.Adjustment;
56 import android.service.notification.NotificationListenerService;
57 import android.service.notification.NotificationRecordProto;
58 import android.service.notification.NotificationStats;
59 import android.service.notification.SnoozeCriterion;
60 import android.service.notification.StatusBarNotification;
61 import android.text.TextUtils;
62 import android.util.ArraySet;
63 import android.util.Log;
64 import android.util.TimeUtils;
65 import android.util.proto.ProtoOutputStream;
66 import android.widget.RemoteViews;
67 
68 import com.android.internal.annotations.VisibleForTesting;
69 import com.android.internal.logging.MetricsLogger;
70 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
71 import com.android.server.EventLogTags;
72 import com.android.server.LocalServices;
73 
74 import java.io.PrintWriter;
75 import java.lang.reflect.Array;
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.List;
79 import java.util.Objects;
80 
81 /**
82  * Holds data about notifications that should not be shared with the
83  * {@link android.service.notification.NotificationListenerService}s.
84  *
85  * <p>These objects should not be mutated unless the code is synchronized
86  * on {@link NotificationManagerService#mNotificationLock}, and any
87  * modification should be followed by a sorting of that list.</p>
88  *
89  * <p>Is sortable by {@link NotificationComparator}.</p>
90  *
91  * {@hide}
92  */
93 public final class NotificationRecord {
94     static final String TAG = "NotificationRecord";
95     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
96     private static final int MAX_LOGTAG_LENGTH = 35;
97     final StatusBarNotification sbn;
98     IActivityManager mAm;
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     private long mUpdateTimeMs;
130 
131     // Is this record an update of an old record?
132     public boolean isUpdate;
133     private int mPackagePriority;
134 
135     private int mAuthoritativeRank;
136     private String mGlobalSortKey;
137     private int mPackageVisibility;
138     private int mUserImportance = IMPORTANCE_UNSPECIFIED;
139     private int mImportance = IMPORTANCE_UNSPECIFIED;
140     private CharSequence mImportanceExplanation = null;
141 
142     private int mSuppressedVisualEffects = 0;
143     private String mUserExplanation;
144     private String mPeopleExplanation;
145     private boolean mPreChannelsNotification = true;
146     private Uri mSound;
147     private long[] mVibration;
148     private AudioAttributes mAttributes;
149     private NotificationChannel mChannel;
150     private ArrayList<String> mPeopleOverride;
151     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
152     private boolean mShowBadge;
153     private LogMaker mLogMaker;
154     private Light mLight;
155     private String mGroupLogTag;
156     private String mChannelIdLogTag;
157 
158     private final List<Adjustment> mAdjustments;
159     private final NotificationStats mStats;
160     private int mUserSentiment;
161     private boolean mIsInterruptive;
162     private boolean mTextChanged;
163     private boolean mRecordedInterruption;
164     private int mNumberOfSmartRepliesAdded;
165     private boolean mHasSeenSmartReplies;
166     /**
167      * Whether this notification (and its channels) should be considered user locked. Used in
168      * conjunction with user sentiment calculation.
169      */
170     private boolean mIsAppImportanceLocked;
171     private ArraySet<Uri> mGrantableUris;
172 
NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel)173     public NotificationRecord(Context context, StatusBarNotification sbn,
174             NotificationChannel channel) {
175         this.sbn = sbn;
176         mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
177                 .getPackageTargetSdkVersion(sbn.getPackageName());
178         mAm = ActivityManager.getService();
179         mOriginalFlags = sbn.getNotification().flags;
180         mRankingTimeMs = calculateRankingTimeMs(0L);
181         mCreationTimeMs = sbn.getPostTime();
182         mUpdateTimeMs = mCreationTimeMs;
183         mContext = context;
184         stats = new NotificationUsageStats.SingleNotificationStats();
185         mChannel = channel;
186         mPreChannelsNotification = isPreChannelsNotification();
187         mSound = calculateSound();
188         mVibration = calculateVibration();
189         mAttributes = calculateAttributes();
190         mImportance = calculateImportance();
191         mLight = calculateLights();
192         mAdjustments = new ArrayList<>();
193         mStats = new NotificationStats();
194         calculateUserSentiment();
195         calculateGrantableUris();
196     }
197 
isPreChannelsNotification()198     private boolean isPreChannelsNotification() {
199         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) {
200             if (mTargetSdkVersion < Build.VERSION_CODES.O) {
201                 return true;
202             }
203         }
204         return false;
205     }
206 
calculateSound()207     private Uri calculateSound() {
208         final Notification n = sbn.getNotification();
209 
210         // No notification sounds on tv
211         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
212             return null;
213         }
214 
215         Uri sound = mChannel.getSound();
216         if (mPreChannelsNotification && (getChannel().getUserLockedFields()
217                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
218 
219             final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0;
220             if (useDefaultSound) {
221                 sound = Settings.System.DEFAULT_NOTIFICATION_URI;
222             } else {
223                 sound = n.sound;
224             }
225         }
226         return sound;
227     }
228 
calculateLights()229     private Light calculateLights() {
230         int defaultLightColor = mContext.getResources().getColor(
231                 com.android.internal.R.color.config_defaultNotificationColor);
232         int defaultLightOn = mContext.getResources().getInteger(
233                 com.android.internal.R.integer.config_defaultNotificationLedOn);
234         int defaultLightOff = mContext.getResources().getInteger(
235                 com.android.internal.R.integer.config_defaultNotificationLedOff);
236 
237         int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor()
238                 : defaultLightColor;
239         Light light = getChannel().shouldShowLights() ? new Light(channelLightColor,
240                 defaultLightOn, defaultLightOff) : null;
241         if (mPreChannelsNotification
242                 && (getChannel().getUserLockedFields()
243                 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
244             final Notification notification = sbn.getNotification();
245             if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
246                 light = new Light(notification.ledARGB, notification.ledOnMS,
247                         notification.ledOffMS);
248                 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
249                     light = new Light(defaultLightColor, defaultLightOn,
250                             defaultLightOff);
251                 }
252             } else {
253                 light = null;
254             }
255         }
256         return light;
257     }
258 
calculateVibration()259     private long[] calculateVibration() {
260         long[] vibration;
261         final long[] defaultVibration =  NotificationManagerService.getLongArray(
262                 mContext.getResources(),
263                 com.android.internal.R.array.config_defaultNotificationVibePattern,
264                 NotificationManagerService.VIBRATE_PATTERN_MAXLEN,
265                 NotificationManagerService.DEFAULT_VIBRATE_PATTERN);
266         if (getChannel().shouldVibrate()) {
267             vibration = getChannel().getVibrationPattern() == null
268                     ? defaultVibration : getChannel().getVibrationPattern();
269         } else {
270             vibration = null;
271         }
272         if (mPreChannelsNotification
273                 && (getChannel().getUserLockedFields()
274                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
275             final Notification notification = sbn.getNotification();
276             final boolean useDefaultVibrate =
277                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
278             if (useDefaultVibrate) {
279                 vibration = defaultVibration;
280             } else {
281                 vibration = notification.vibrate;
282             }
283         }
284         return vibration;
285     }
286 
calculateAttributes()287     private AudioAttributes calculateAttributes() {
288         final Notification n = sbn.getNotification();
289         AudioAttributes attributes = getChannel().getAudioAttributes();
290         if (attributes == null) {
291             attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
292         }
293 
294         if (mPreChannelsNotification
295                 && (getChannel().getUserLockedFields()
296                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
297             if (n.audioAttributes != null) {
298                 // prefer audio attributes to stream type
299                 attributes = n.audioAttributes;
300             } else if (n.audioStreamType >= 0
301                     && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
302                 // the stream type is valid, use it
303                 attributes = new AudioAttributes.Builder()
304                         .setInternalLegacyStreamType(n.audioStreamType)
305                         .build();
306             } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
307                 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
308             }
309         }
310         return attributes;
311     }
312 
calculateImportance()313     private int calculateImportance() {
314         final Notification n = sbn.getNotification();
315         int importance = getChannel().getImportance();
316         int requestedImportance = IMPORTANCE_DEFAULT;
317 
318         // Migrate notification flags to scores
319         if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
320             n.priority = Notification.PRIORITY_MAX;
321         }
322 
323         n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN,
324                 Notification.PRIORITY_MAX);
325         switch (n.priority) {
326             case Notification.PRIORITY_MIN:
327                 requestedImportance = IMPORTANCE_MIN;
328                 break;
329             case Notification.PRIORITY_LOW:
330                 requestedImportance = IMPORTANCE_LOW;
331                 break;
332             case Notification.PRIORITY_DEFAULT:
333                 requestedImportance = IMPORTANCE_DEFAULT;
334                 break;
335             case Notification.PRIORITY_HIGH:
336             case Notification.PRIORITY_MAX:
337                 requestedImportance = IMPORTANCE_HIGH;
338                 break;
339         }
340         stats.requestedImportance = requestedImportance;
341         stats.isNoisy = mSound != null || mVibration != null;
342 
343         if (mPreChannelsNotification
344                 && (importance == IMPORTANCE_UNSPECIFIED
345                 || (getChannel().getUserLockedFields()
346                 & USER_LOCKED_IMPORTANCE) == 0)) {
347             if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
348                 requestedImportance = IMPORTANCE_LOW;
349             }
350 
351             if (stats.isNoisy) {
352                 if (requestedImportance < IMPORTANCE_DEFAULT) {
353                     requestedImportance = IMPORTANCE_DEFAULT;
354                 }
355             }
356 
357             if (n.fullScreenIntent != null) {
358                 requestedImportance = IMPORTANCE_HIGH;
359             }
360             importance = requestedImportance;
361         }
362 
363         stats.naturalImportance = importance;
364         return importance;
365     }
366 
367     // copy any notes that the ranking system may have made before the update
copyRankingInformation(NotificationRecord previous)368     public void copyRankingInformation(NotificationRecord previous) {
369         mContactAffinity = previous.mContactAffinity;
370         mRecentlyIntrusive = previous.mRecentlyIntrusive;
371         mPackagePriority = previous.mPackagePriority;
372         mPackageVisibility = previous.mPackageVisibility;
373         mIntercept = previous.mIntercept;
374         mHidden = previous.mHidden;
375         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
376         mCreationTimeMs = previous.mCreationTimeMs;
377         mVisibleSinceMs = previous.mVisibleSinceMs;
378         if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
379             sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
380         }
381         // Don't copy importance information or mGlobalSortKey, recompute them.
382     }
383 
getNotification()384     public Notification getNotification() { return sbn.getNotification(); }
getFlags()385     public int getFlags() { return sbn.getNotification().flags; }
getUser()386     public UserHandle getUser() { return sbn.getUser(); }
getKey()387     public String getKey() { return sbn.getKey(); }
388     /** @deprecated Use {@link #getUser()} instead. */
getUserId()389     public int getUserId() { return sbn.getUserId(); }
getUid()390     public int getUid() { return sbn.getUid(); }
391 
dump(ProtoOutputStream proto, long fieldId, boolean redact, int state)392     void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) {
393         final long token = proto.start(fieldId);
394 
395         proto.write(NotificationRecordProto.KEY, sbn.getKey());
396         proto.write(NotificationRecordProto.STATE, state);
397         if (getChannel() != null) {
398             proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
399         }
400         proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null);
401         proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null);
402         proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags);
403         proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey());
404         proto.write(NotificationRecordProto.IMPORTANCE, getImportance());
405         if (getSound() != null) {
406             proto.write(NotificationRecordProto.SOUND, getSound().toString());
407         }
408         if (getAudioAttributes() != null) {
409             getAudioAttributes().writeToProto(proto, NotificationRecordProto.AUDIO_ATTRIBUTES);
410         }
411 
412         proto.end(token);
413     }
414 
formatRemoteViews(RemoteViews rv)415     String formatRemoteViews(RemoteViews rv) {
416         if (rv == null) return "null";
417         return String.format("%s/0x%08x (%d bytes): %s",
418             rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString());
419     }
420 
dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)421     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
422         final Notification notification = sbn.getNotification();
423         final Icon icon = notification.getSmallIcon();
424         String iconStr = String.valueOf(icon);
425         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
426             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
427         }
428         pw.println(prefix + this);
429         prefix = prefix + "  ";
430         pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
431         pw.println(prefix + "icon=" + iconStr);
432         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
433         pw.println(prefix + "pri=" + notification.priority);
434         pw.println(prefix + "key=" + sbn.getKey());
435         pw.println(prefix + "seen=" + mStats.hasSeen());
436         pw.println(prefix + "groupKey=" + getGroupKey());
437         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
438         pw.println(prefix + "contentIntent=" + notification.contentIntent);
439         pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
440 
441         pw.print(prefix + "tickerText=");
442         if (!TextUtils.isEmpty(notification.tickerText)) {
443             final String ticker = notification.tickerText.toString();
444             if (redact) {
445                 // if the string is long enough, we allow ourselves a few bytes for debugging
446                 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : "");
447                 pw.println("...");
448             } else {
449                 pw.println(ticker);
450             }
451         } else {
452             pw.println("null");
453         }
454         pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
455         pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
456         pw.println(prefix + "headsUpContentView="
457                 + formatRemoteViews(notification.headsUpContentView));
458         pw.print(prefix + String.format("color=0x%08x", notification.color));
459         pw.println(prefix + "timeout="
460                 + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
461         if (notification.actions != null && notification.actions.length > 0) {
462             pw.println(prefix + "actions={");
463             final int N = notification.actions.length;
464             for (int i = 0; i < N; i++) {
465                 final Notification.Action action = notification.actions[i];
466                 if (action != null) {
467                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
468                             prefix,
469                             i,
470                             action.title,
471                             action.actionIntent == null ? "null" : action.actionIntent.toString()
472                     ));
473                 }
474             }
475             pw.println(prefix + "  }");
476         }
477         if (notification.extras != null && notification.extras.size() > 0) {
478             pw.println(prefix + "extras={");
479             for (String key : notification.extras.keySet()) {
480                 pw.print(prefix + "    " + key + "=");
481                 Object val = notification.extras.get(key);
482                 if (val == null) {
483                     pw.println("null");
484                 } else {
485                     pw.print(val.getClass().getSimpleName());
486                     if (redact && (val instanceof CharSequence || val instanceof String)) {
487                         // redact contents from bugreports
488                     } else if (val instanceof Bitmap) {
489                         pw.print(String.format(" (%dx%d)",
490                                 ((Bitmap) val).getWidth(),
491                                 ((Bitmap) val).getHeight()));
492                     } else if (val.getClass().isArray()) {
493                         final int N = Array.getLength(val);
494                         pw.print(" (" + N + ")");
495                         if (!redact) {
496                             for (int j = 0; j < N; j++) {
497                                 pw.println();
498                                 pw.print(String.format("%s      [%d] %s",
499                                         prefix, j, String.valueOf(Array.get(val, j))));
500                             }
501                         }
502                     } else {
503                         pw.print(" (" + String.valueOf(val) + ")");
504                     }
505                     pw.println();
506                 }
507             }
508             pw.println(prefix + "}");
509         }
510         pw.println(prefix + "stats=" + stats.toString());
511         pw.println(prefix + "mContactAffinity=" + mContactAffinity);
512         pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
513         pw.println(prefix + "mPackagePriority=" + mPackagePriority);
514         pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
515         pw.println(prefix + "mUserImportance="
516                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
517         pw.println(prefix + "mImportance="
518                 + NotificationListenerService.Ranking.importanceToString(mImportance));
519         pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
520         pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
521         pw.println(prefix + "mIntercept=" + mIntercept);
522         pw.println(prefix + "mHidden==" + mHidden);
523         pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
524         pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
525         pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
526         pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
527         pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
528         pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
529         if (mPreChannelsNotification) {
530             pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
531                     notification.defaults, notification.flags));
532             pw.println(prefix + "n.sound=" + notification.sound);
533             pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
534             pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
535             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
536                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
537             pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
538         }
539         pw.println(prefix + "mSound= " + mSound);
540         pw.println(prefix + "mVibration= " + mVibration);
541         pw.println(prefix + "mAttributes= " + mAttributes);
542         pw.println(prefix + "mLight= " + mLight);
543         pw.println(prefix + "mShowBadge=" + mShowBadge);
544         pw.println(prefix + "mColorized=" + notification.isColorized());
545         pw.println(prefix + "mIsInterruptive=" + mIsInterruptive);
546         pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
547         if (getPeopleOverride() != null) {
548             pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
549         }
550         if (getSnoozeCriteria() != null) {
551             pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
552         }
553         pw.println(prefix + "mAdjustments=" + mAdjustments);
554     }
555 
556 
idDebugString(Context baseContext, String packageName, int id)557     static String idDebugString(Context baseContext, String packageName, int id) {
558         Context c;
559 
560         if (packageName != null) {
561             try {
562                 c = baseContext.createPackageContext(packageName, 0);
563             } catch (NameNotFoundException e) {
564                 c = baseContext;
565             }
566         } else {
567             c = baseContext;
568         }
569 
570         Resources r = c.getResources();
571         try {
572             return r.getResourceName(id);
573         } catch (Resources.NotFoundException e) {
574             return "<name unknown>";
575         }
576     }
577 
578     @Override
toString()579     public final String toString() {
580         return String.format(
581                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
582                         "appImportanceLocked=%s: %s)",
583                 System.identityHashCode(this),
584                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
585                 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
586                 mIsAppImportanceLocked, this.sbn.getNotification());
587     }
588 
addAdjustment(Adjustment adjustment)589     public void addAdjustment(Adjustment adjustment) {
590         synchronized (mAdjustments) {
591             mAdjustments.add(adjustment);
592         }
593     }
594 
applyAdjustments()595     public void applyAdjustments() {
596         synchronized (mAdjustments) {
597             for (Adjustment adjustment: mAdjustments) {
598                 Bundle signals = adjustment.getSignals();
599                 if (signals.containsKey(Adjustment.KEY_PEOPLE)) {
600                     final ArrayList<String> people =
601                             adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
602                     setPeopleOverride(people);
603                 }
604                 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
605                     final ArrayList<SnoozeCriterion> snoozeCriterionList =
606                             adjustment.getSignals().getParcelableArrayList(
607                                     Adjustment.KEY_SNOOZE_CRITERIA);
608                     setSnoozeCriteria(snoozeCriterionList);
609                 }
610                 if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
611                     final String groupOverrideKey =
612                             adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
613                     setOverrideGroupKey(groupOverrideKey);
614                 }
615                 if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
616                     // Only allow user sentiment update from assistant if user hasn't already
617                     // expressed a preference for this channel
618                     if (!mIsAppImportanceLocked
619                             && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
620                         setUserSentiment(adjustment.getSignals().getInt(
621                                 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
622                     }
623                 }
624             }
625         }
626     }
627 
setIsAppImportanceLocked(boolean isAppImportanceLocked)628     public void setIsAppImportanceLocked(boolean isAppImportanceLocked) {
629         mIsAppImportanceLocked = isAppImportanceLocked;
630         calculateUserSentiment();
631     }
632 
setContactAffinity(float contactAffinity)633     public void setContactAffinity(float contactAffinity) {
634         mContactAffinity = contactAffinity;
635         if (mImportance < IMPORTANCE_DEFAULT &&
636                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
637             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
638         }
639     }
640 
getContactAffinity()641     public float getContactAffinity() {
642         return mContactAffinity;
643     }
644 
setRecentlyIntrusive(boolean recentlyIntrusive)645     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
646         mRecentlyIntrusive = recentlyIntrusive;
647         if (recentlyIntrusive) {
648             mLastIntrusive = System.currentTimeMillis();
649         }
650     }
651 
isRecentlyIntrusive()652     public boolean isRecentlyIntrusive() {
653         return mRecentlyIntrusive;
654     }
655 
getLastIntrusive()656     public long getLastIntrusive() {
657         return mLastIntrusive;
658     }
659 
setPackagePriority(int packagePriority)660     public void setPackagePriority(int packagePriority) {
661         mPackagePriority = packagePriority;
662     }
663 
getPackagePriority()664     public int getPackagePriority() {
665         return mPackagePriority;
666     }
667 
setPackageVisibilityOverride(int packageVisibility)668     public void setPackageVisibilityOverride(int packageVisibility) {
669         mPackageVisibility = packageVisibility;
670     }
671 
getPackageVisibilityOverride()672     public int getPackageVisibilityOverride() {
673         return mPackageVisibility;
674     }
675 
setUserImportance(int importance)676     public void setUserImportance(int importance) {
677         mUserImportance = importance;
678         applyUserImportance();
679     }
680 
getUserExplanation()681     private String getUserExplanation() {
682         if (mUserExplanation == null) {
683             mUserExplanation = mContext.getResources().getString(
684                     com.android.internal.R.string.importance_from_user);
685         }
686         return mUserExplanation;
687     }
688 
getPeopleExplanation()689     private String getPeopleExplanation() {
690         if (mPeopleExplanation == null) {
691             mPeopleExplanation = mContext.getResources().getString(
692                     com.android.internal.R.string.importance_from_person);
693         }
694         return mPeopleExplanation;
695     }
696 
applyUserImportance()697     private void applyUserImportance() {
698         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
699             mImportance = mUserImportance;
700             mImportanceExplanation = getUserExplanation();
701         }
702     }
703 
getUserImportance()704     public int getUserImportance() {
705         return mUserImportance;
706     }
707 
setImportance(int importance, CharSequence explanation)708     public void setImportance(int importance, CharSequence explanation) {
709         if (importance != IMPORTANCE_UNSPECIFIED) {
710             mImportance = importance;
711             mImportanceExplanation = explanation;
712         }
713         applyUserImportance();
714     }
715 
getImportance()716     public int getImportance() {
717         return mImportance;
718     }
719 
getImportanceExplanation()720     public CharSequence getImportanceExplanation() {
721         return mImportanceExplanation;
722     }
723 
setIntercepted(boolean intercept)724     public boolean setIntercepted(boolean intercept) {
725         mIntercept = intercept;
726         return mIntercept;
727     }
728 
isIntercepted()729     public boolean isIntercepted() {
730         return mIntercept;
731     }
732 
setHidden(boolean hidden)733     public void setHidden(boolean hidden) {
734         mHidden = hidden;
735     }
736 
isHidden()737     public boolean isHidden() {
738         return mHidden;
739     }
740 
741 
setSuppressedVisualEffects(int effects)742     public void setSuppressedVisualEffects(int effects) {
743         mSuppressedVisualEffects = effects;
744     }
745 
getSuppressedVisualEffects()746     public int getSuppressedVisualEffects() {
747         return mSuppressedVisualEffects;
748     }
749 
isCategory(String category)750     public boolean isCategory(String category) {
751         return Objects.equals(getNotification().category, category);
752     }
753 
isAudioAttributesUsage(int usage)754     public boolean isAudioAttributesUsage(int usage) {
755         return mAttributes != null && mAttributes.getUsage() == usage;
756     }
757 
758     /**
759      * Returns the timestamp to use for time-based sorting in the ranker.
760      */
getRankingTimeMs()761     public long getRankingTimeMs() {
762         return mRankingTimeMs;
763     }
764 
765     /**
766      * @param now this current time in milliseconds.
767      * @returns the number of milliseconds since the most recent update, or the post time if none.
768      */
getFreshnessMs(long now)769     public int getFreshnessMs(long now) {
770         return (int) (now - mUpdateTimeMs);
771     }
772 
773     /**
774      * @param now this current time in milliseconds.
775      * @returns the number of milliseconds since the the first post, ignoring updates.
776      */
getLifespanMs(long now)777     public int getLifespanMs(long now) {
778         return (int) (now - mCreationTimeMs);
779     }
780 
781     /**
782      * @param now this current time in milliseconds.
783      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
784      */
getExposureMs(long now)785     public int getExposureMs(long now) {
786         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
787     }
788 
789     /**
790      * Set the visibility of the notification.
791      */
setVisibility(boolean visible, int rank, int count)792     public void setVisibility(boolean visible, int rank, int count) {
793         final long now = System.currentTimeMillis();
794         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
795         stats.onVisibilityChanged(visible);
796         MetricsLogger.action(getLogMaker(now)
797                 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
798                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
799                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank)
800                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count));
801         if (visible) {
802             setSeen();
803             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
804         }
805         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
806                 getLifespanMs(now),
807                 getFreshnessMs(now),
808                 0, // exposure time
809                 rank);
810     }
811 
812     /**
813      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
814      *     of the previous notification record, 0 otherwise
815      */
calculateRankingTimeMs(long previousRankingTimeMs)816     private long calculateRankingTimeMs(long previousRankingTimeMs) {
817         Notification n = getNotification();
818         // Take developer provided 'when', unless it's in the future.
819         if (n.when != 0 && n.when <= sbn.getPostTime()) {
820             return n.when;
821         }
822         // If we've ranked a previous instance with a timestamp, inherit it. This case is
823         // important in order to have ranking stability for updating notifications.
824         if (previousRankingTimeMs > 0) {
825             return previousRankingTimeMs;
826         }
827         return sbn.getPostTime();
828     }
829 
setGlobalSortKey(String globalSortKey)830     public void setGlobalSortKey(String globalSortKey) {
831         mGlobalSortKey = globalSortKey;
832     }
833 
getGlobalSortKey()834     public String getGlobalSortKey() {
835         return mGlobalSortKey;
836     }
837 
838     /** Check if any of the listeners have marked this notification as seen by the user. */
isSeen()839     public boolean isSeen() {
840         return mStats.hasSeen();
841     }
842 
843     /** Mark the notification as seen by the user. */
setSeen()844     public void setSeen() {
845         mStats.setSeen();
846         if (mTextChanged) {
847             mIsInterruptive = true;
848         }
849     }
850 
setAuthoritativeRank(int authoritativeRank)851     public void setAuthoritativeRank(int authoritativeRank) {
852         mAuthoritativeRank = authoritativeRank;
853     }
854 
getAuthoritativeRank()855     public int getAuthoritativeRank() {
856         return mAuthoritativeRank;
857     }
858 
getGroupKey()859     public String getGroupKey() {
860         return sbn.getGroupKey();
861     }
862 
setOverrideGroupKey(String overrideGroupKey)863     public void setOverrideGroupKey(String overrideGroupKey) {
864         sbn.setOverrideGroupKey(overrideGroupKey);
865         mGroupLogTag = null;
866     }
867 
getGroupLogTag()868     private String getGroupLogTag() {
869         if (mGroupLogTag == null) {
870             mGroupLogTag = shortenTag(sbn.getGroup());
871         }
872         return mGroupLogTag;
873     }
874 
getChannelIdLogTag()875     private String getChannelIdLogTag() {
876         if (mChannelIdLogTag == null) {
877             mChannelIdLogTag = shortenTag(mChannel.getId());
878         }
879         return mChannelIdLogTag;
880     }
881 
shortenTag(String longTag)882     private String shortenTag(String longTag) {
883         if (longTag == null) {
884             return null;
885         }
886         if (longTag.length() < MAX_LOGTAG_LENGTH) {
887             return longTag;
888         } else {
889             return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
890                     Integer.toHexString(longTag.hashCode());
891         }
892     }
893 
getChannel()894     public NotificationChannel getChannel() {
895         return mChannel;
896     }
897 
898     /**
899      * @see RankingHelper#getIsAppImportanceLocked(String, int)
900      */
getIsAppImportanceLocked()901     public boolean getIsAppImportanceLocked() {
902         return mIsAppImportanceLocked;
903     }
904 
updateNotificationChannel(NotificationChannel channel)905     protected void updateNotificationChannel(NotificationChannel channel) {
906         if (channel != null) {
907             mChannel = channel;
908             calculateImportance();
909             calculateUserSentiment();
910         }
911     }
912 
setShowBadge(boolean showBadge)913     public void setShowBadge(boolean showBadge) {
914         mShowBadge = showBadge;
915     }
916 
canShowBadge()917     public boolean canShowBadge() {
918         return mShowBadge;
919     }
920 
getLight()921     public Light getLight() {
922         return mLight;
923     }
924 
getSound()925     public Uri getSound() {
926         return mSound;
927     }
928 
getVibration()929     public long[] getVibration() {
930         return mVibration;
931     }
932 
getAudioAttributes()933     public AudioAttributes getAudioAttributes() {
934         return mAttributes;
935     }
936 
getPeopleOverride()937     public ArrayList<String> getPeopleOverride() {
938         return mPeopleOverride;
939     }
940 
setInterruptive(boolean interruptive)941     public void setInterruptive(boolean interruptive) {
942         mIsInterruptive = interruptive;
943     }
944 
setTextChanged(boolean textChanged)945     public void setTextChanged(boolean textChanged) {
946         mTextChanged = textChanged;
947     }
948 
setRecordedInterruption(boolean recorded)949     public void setRecordedInterruption(boolean recorded) {
950         mRecordedInterruption = recorded;
951     }
952 
hasRecordedInterruption()953     public boolean hasRecordedInterruption() {
954         return mRecordedInterruption;
955     }
956 
isInterruptive()957     public boolean isInterruptive() {
958         return mIsInterruptive;
959     }
960 
setPeopleOverride(ArrayList<String> people)961     protected void setPeopleOverride(ArrayList<String> people) {
962         mPeopleOverride = people;
963     }
964 
getSnoozeCriteria()965     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
966         return mSnoozeCriteria;
967     }
968 
setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria)969     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
970         mSnoozeCriteria = snoozeCriteria;
971     }
972 
calculateUserSentiment()973     private void calculateUserSentiment() {
974         if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0
975                 || mIsAppImportanceLocked) {
976             mUserSentiment = USER_SENTIMENT_POSITIVE;
977         }
978     }
979 
setUserSentiment(int userSentiment)980     private void setUserSentiment(int userSentiment) {
981         mUserSentiment = userSentiment;
982     }
983 
getUserSentiment()984     public int getUserSentiment() {
985         return mUserSentiment;
986     }
987 
getStats()988     public NotificationStats getStats() {
989         return mStats;
990     }
991 
recordExpanded()992     public void recordExpanded() {
993         mStats.setExpanded();
994     }
995 
recordDirectReplied()996     public void recordDirectReplied() {
997         mStats.setDirectReplied();
998     }
999 
recordDismissalSurface(@otificationStats.DismissalSurface int surface)1000     public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
1001         mStats.setDismissalSurface(surface);
1002     }
1003 
recordSnoozed()1004     public void recordSnoozed() {
1005         mStats.setSnoozed();
1006     }
1007 
recordViewedSettings()1008     public void recordViewedSettings() {
1009         mStats.setViewedSettings();
1010     }
1011 
setNumSmartRepliesAdded(int noReplies)1012     public void setNumSmartRepliesAdded(int noReplies) {
1013         mNumberOfSmartRepliesAdded = noReplies;
1014     }
1015 
getNumSmartRepliesAdded()1016     public int getNumSmartRepliesAdded() {
1017         return mNumberOfSmartRepliesAdded;
1018     }
1019 
hasSeenSmartReplies()1020     public boolean hasSeenSmartReplies() {
1021         return mHasSeenSmartReplies;
1022     }
1023 
setSeenSmartReplies(boolean hasSeenSmartReplies)1024     public void setSeenSmartReplies(boolean hasSeenSmartReplies) {
1025         mHasSeenSmartReplies = hasSeenSmartReplies;
1026     }
1027 
1028     /**
1029      * @return all {@link Uri} that should have permission granted to whoever
1030      *         will be rendering it. This list has already been vetted to only
1031      *         include {@link Uri} that the enqueuing app can grant.
1032      */
getGrantableUris()1033     public @Nullable ArraySet<Uri> getGrantableUris() {
1034         return mGrantableUris;
1035     }
1036 
1037     /**
1038      * Collect all {@link Uri} that should have permission granted to whoever
1039      * will be rendering it.
1040      */
calculateGrantableUris()1041     protected void calculateGrantableUris() {
1042         final Notification notification = getNotification();
1043         notification.visitUris((uri) -> {
1044             visitGrantableUri(uri, false);
1045         });
1046 
1047         if (notification.getChannelId() != null) {
1048             NotificationChannel channel = getChannel();
1049             if (channel != null) {
1050                 visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
1051                         & NotificationChannel.USER_LOCKED_SOUND) != 0);
1052             }
1053         }
1054     }
1055 
1056     /**
1057      * Note the presence of a {@link Uri} that should have permission granted to
1058      * whoever will be rendering it.
1059      * <p>
1060      * If the enqueuing app has the ability to grant access, it will be added to
1061      * {@link #mGrantableUris}. Otherwise, this will either log or throw
1062      * {@link SecurityException} depending on target SDK of enqueuing app.
1063      */
visitGrantableUri(Uri uri, boolean userOverriddenUri)1064     private void visitGrantableUri(Uri uri, boolean userOverriddenUri) {
1065         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1066 
1067         // We can't grant Uri permissions from system
1068         final int sourceUid = sbn.getUid();
1069         if (sourceUid == android.os.Process.SYSTEM_UID) return;
1070 
1071         final long ident = Binder.clearCallingIdentity();
1072         try {
1073             // This will throw SecurityException if caller can't grant
1074             mAm.checkGrantUriPermission(sourceUid, null,
1075                     ContentProvider.getUriWithoutUserId(uri),
1076                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
1077                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
1078 
1079             if (mGrantableUris == null) {
1080                 mGrantableUris = new ArraySet<>();
1081             }
1082             mGrantableUris.add(uri);
1083         } catch (RemoteException ignored) {
1084             // Ignored because we're in same process
1085         } catch (SecurityException e) {
1086             if (!userOverriddenUri) {
1087                 if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
1088                     throw e;
1089                 } else {
1090                     Log.w(TAG, "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
1091                 }
1092             }
1093         } finally {
1094             Binder.restoreCallingIdentity(ident);
1095         }
1096     }
1097 
getLogMaker(long now)1098     public LogMaker getLogMaker(long now) {
1099         if (mLogMaker == null) {
1100             // initialize fields that only change on update (so a new record)
1101             mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
1102                     .setPackageName(sbn.getPackageName())
1103                     .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
1104                     .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
1105                     .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
1106         }
1107         // reset fields that can change between updates, or are used by multiple logs
1108         return mLogMaker
1109                 .clearCategory()
1110                 .clearType()
1111                 .clearSubtype()
1112                 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
1113                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
1114                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
1115                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
1116                         sbn.getNotification().isGroupSummary() ? 1 : 0)
1117                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
1118                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
1119                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
1120     }
1121 
getLogMaker()1122     public LogMaker getLogMaker() {
1123         return getLogMaker(System.currentTimeMillis());
1124     }
1125 
1126     @VisibleForTesting
1127     static final class Light {
1128         public final int color;
1129         public final int onMs;
1130         public final int offMs;
1131 
Light(int color, int onMs, int offMs)1132         public Light(int color, int onMs, int offMs) {
1133             this.color = color;
1134             this.onMs = onMs;
1135             this.offMs = offMs;
1136         }
1137 
1138         @Override
equals(Object o)1139         public boolean equals(Object o) {
1140             if (this == o) return true;
1141             if (o == null || getClass() != o.getClass()) return false;
1142 
1143             Light light = (Light) o;
1144 
1145             if (color != light.color) return false;
1146             if (onMs != light.onMs) return false;
1147             return offMs == light.offMs;
1148 
1149         }
1150 
1151         @Override
hashCode()1152         public int hashCode() {
1153             int result = color;
1154             result = 31 * result + onMs;
1155             result = 31 * result + offMs;
1156             return result;
1157         }
1158 
1159         @Override
toString()1160         public String toString() {
1161             return "Light{" +
1162                     "color=" + color +
1163                     ", onMs=" + onMs +
1164                     ", offMs=" + offMs +
1165                     '}';
1166         }
1167     }
1168 }
1169