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.NotificationManager.IMPORTANCE_MIN;
19 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
20 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
21 import static android.app.NotificationManager.IMPORTANCE_HIGH;
22 import static android.app.NotificationManager.IMPORTANCE_LOW;
23 
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.drawable.Icon;
33 import android.media.AudioAttributes;
34 import android.media.AudioSystem;
35 import android.metrics.LogMaker;
36 import android.net.Uri;
37 import android.os.Build;
38 import android.os.UserHandle;
39 import android.provider.Settings;
40 import android.service.notification.NotificationListenerService;
41 import android.service.notification.NotificationRecordProto;
42 import android.service.notification.SnoozeCriterion;
43 import android.service.notification.StatusBarNotification;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.util.Slog;
47 import android.util.TimeUtils;
48 import android.util.proto.ProtoOutputStream;
49 import android.widget.RemoteViews;
50 
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.logging.MetricsLogger;
53 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
54 import com.android.server.EventLogTags;
55 
56 import java.io.PrintWriter;
57 import java.lang.reflect.Array;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Objects;
61 
62 /**
63  * Holds data about notifications that should not be shared with the
64  * {@link android.service.notification.NotificationListenerService}s.
65  *
66  * <p>These objects should not be mutated unless the code is synchronized
67  * on {@link NotificationManagerService#mNotificationLock}, and any
68  * modification should be followed by a sorting of that list.</p>
69  *
70  * <p>Is sortable by {@link NotificationComparator}.</p>
71  *
72  * {@hide}
73  */
74 public final class NotificationRecord {
75     static final String TAG = "NotificationRecord";
76     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
77     private static final int MAX_LOGTAG_LENGTH = 35;
78     final StatusBarNotification sbn;
79     final int mOriginalFlags;
80     private final Context mContext;
81 
82     NotificationUsageStats.SingleNotificationStats stats;
83     boolean isCanceled;
84     /** Whether the notification was seen by the user via one of the notification listeners. */
85     boolean mIsSeen;
86 
87     // These members are used by NotificationSignalExtractors
88     // to communicate with the ranking module.
89     private float mContactAffinity;
90     private boolean mRecentlyIntrusive;
91     private long mLastIntrusive;
92 
93     // is this notification currently being intercepted by Zen Mode?
94     private boolean mIntercept;
95 
96     // The timestamp used for ranking.
97     private long mRankingTimeMs;
98 
99     // The first post time, stable across updates.
100     private long mCreationTimeMs;
101 
102     // The most recent visibility event.
103     private long mVisibleSinceMs;
104 
105     // The most recent update time, or the creation time if no updates.
106     private long mUpdateTimeMs;
107 
108     // Is this record an update of an old record?
109     public boolean isUpdate;
110     private int mPackagePriority;
111 
112     private int mAuthoritativeRank;
113     private String mGlobalSortKey;
114     private int mPackageVisibility;
115     private int mUserImportance = IMPORTANCE_UNSPECIFIED;
116     private int mImportance = IMPORTANCE_UNSPECIFIED;
117     private CharSequence mImportanceExplanation = null;
118 
119     private int mSuppressedVisualEffects = 0;
120     private String mUserExplanation;
121     private String mPeopleExplanation;
122     private boolean mPreChannelsNotification = true;
123     private Uri mSound;
124     private long[] mVibration;
125     private AudioAttributes mAttributes;
126     private NotificationChannel mChannel;
127     private ArrayList<String> mPeopleOverride;
128     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
129     private boolean mShowBadge;
130     private LogMaker mLogMaker;
131     private Light mLight;
132     private String mGroupLogTag;
133     private String mChannelIdLogTag;
134 
135     @VisibleForTesting
NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel)136     public NotificationRecord(Context context, StatusBarNotification sbn,
137             NotificationChannel channel)
138     {
139         this.sbn = sbn;
140         mOriginalFlags = sbn.getNotification().flags;
141         mRankingTimeMs = calculateRankingTimeMs(0L);
142         mCreationTimeMs = sbn.getPostTime();
143         mUpdateTimeMs = mCreationTimeMs;
144         mContext = context;
145         stats = new NotificationUsageStats.SingleNotificationStats();
146         mChannel = channel;
147         mPreChannelsNotification = isPreChannelsNotification();
148         mSound = calculateSound();
149         mVibration = calculateVibration();
150         mAttributes = calculateAttributes();
151         mImportance = calculateImportance();
152         mLight = calculateLights();
153     }
154 
isPreChannelsNotification()155     private boolean isPreChannelsNotification() {
156         try {
157             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) {
158                   final ApplicationInfo applicationInfo =
159                         mContext.getPackageManager().getApplicationInfoAsUser(sbn.getPackageName(),
160                                 0, UserHandle.getUserId(sbn.getUid()));
161                 if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
162                     return true;
163                 }
164             }
165         } catch (NameNotFoundException e) {
166             Slog.e(TAG, "Can't find package", e);
167         }
168         return false;
169     }
170 
calculateSound()171     private Uri calculateSound() {
172         final Notification n = sbn.getNotification();
173 
174         // No notification sounds on tv
175         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
176             return null;
177         }
178 
179         Uri sound = mChannel.getSound();
180         if (mPreChannelsNotification && (getChannel().getUserLockedFields()
181                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
182 
183             final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0;
184             if (useDefaultSound) {
185                 sound = Settings.System.DEFAULT_NOTIFICATION_URI;
186             } else {
187                 sound = n.sound;
188             }
189         }
190         return sound;
191     }
192 
calculateLights()193     private Light calculateLights() {
194         int defaultLightColor = mContext.getResources().getColor(
195                 com.android.internal.R.color.config_defaultNotificationColor);
196         int defaultLightOn = mContext.getResources().getInteger(
197                 com.android.internal.R.integer.config_defaultNotificationLedOn);
198         int defaultLightOff = mContext.getResources().getInteger(
199                 com.android.internal.R.integer.config_defaultNotificationLedOff);
200 
201         int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor()
202                 : defaultLightColor;
203         Light light = getChannel().shouldShowLights() ? new Light(channelLightColor,
204                 defaultLightOn, defaultLightOff) : null;
205         if (mPreChannelsNotification
206                 && (getChannel().getUserLockedFields()
207                 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
208             final Notification notification = sbn.getNotification();
209             if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
210                 light = new Light(notification.ledARGB, notification.ledOnMS,
211                         notification.ledOffMS);
212                 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
213                     light = new Light(defaultLightColor, defaultLightOn,
214                             defaultLightOff);
215                 }
216             } else {
217                 light = null;
218             }
219         }
220         return light;
221     }
222 
calculateVibration()223     private long[] calculateVibration() {
224         long[] vibration;
225         final long[] defaultVibration =  NotificationManagerService.getLongArray(
226                 mContext.getResources(),
227                 com.android.internal.R.array.config_defaultNotificationVibePattern,
228                 NotificationManagerService.VIBRATE_PATTERN_MAXLEN,
229                 NotificationManagerService.DEFAULT_VIBRATE_PATTERN);
230         if (getChannel().shouldVibrate()) {
231             vibration = getChannel().getVibrationPattern() == null
232                     ? defaultVibration : getChannel().getVibrationPattern();
233         } else {
234             vibration = null;
235         }
236         if (mPreChannelsNotification
237                 && (getChannel().getUserLockedFields()
238                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
239             final Notification notification = sbn.getNotification();
240             final boolean useDefaultVibrate =
241                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
242             if (useDefaultVibrate) {
243                 vibration = defaultVibration;
244             } else {
245                 vibration = notification.vibrate;
246             }
247         }
248         return vibration;
249     }
250 
calculateAttributes()251     private AudioAttributes calculateAttributes() {
252         final Notification n = sbn.getNotification();
253         AudioAttributes attributes = getChannel().getAudioAttributes();
254         if (attributes == null) {
255             attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
256         }
257 
258         if (mPreChannelsNotification
259                 && (getChannel().getUserLockedFields()
260                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
261             if (n.audioAttributes != null) {
262                 // prefer audio attributes to stream type
263                 attributes = n.audioAttributes;
264             } else if (n.audioStreamType >= 0
265                     && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
266                 // the stream type is valid, use it
267                 attributes = new AudioAttributes.Builder()
268                         .setInternalLegacyStreamType(n.audioStreamType)
269                         .build();
270             } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
271                 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
272             }
273         }
274         return attributes;
275     }
276 
calculateImportance()277     private int calculateImportance() {
278         final Notification n = sbn.getNotification();
279         int importance = getChannel().getImportance();
280         int requestedImportance = IMPORTANCE_DEFAULT;
281 
282         // Migrate notification flags to scores
283         if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
284             n.priority = Notification.PRIORITY_MAX;
285         }
286 
287         n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN,
288                 Notification.PRIORITY_MAX);
289         switch (n.priority) {
290             case Notification.PRIORITY_MIN:
291                 requestedImportance = IMPORTANCE_MIN;
292                 break;
293             case Notification.PRIORITY_LOW:
294                 requestedImportance = IMPORTANCE_LOW;
295                 break;
296             case Notification.PRIORITY_DEFAULT:
297                 requestedImportance = IMPORTANCE_DEFAULT;
298                 break;
299             case Notification.PRIORITY_HIGH:
300             case Notification.PRIORITY_MAX:
301                 requestedImportance = IMPORTANCE_HIGH;
302                 break;
303         }
304         stats.requestedImportance = requestedImportance;
305         stats.isNoisy = mSound != null || mVibration != null;
306 
307         if (mPreChannelsNotification
308                 && (importance == IMPORTANCE_UNSPECIFIED
309                 || (getChannel().getUserLockedFields()
310                 & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0)) {
311             if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
312                 requestedImportance = IMPORTANCE_LOW;
313             }
314 
315             if (stats.isNoisy) {
316                 if (requestedImportance < IMPORTANCE_DEFAULT) {
317                     requestedImportance = IMPORTANCE_DEFAULT;
318                 }
319             }
320 
321             if (n.fullScreenIntent != null) {
322                 requestedImportance = IMPORTANCE_HIGH;
323             }
324             importance = requestedImportance;
325         }
326 
327         stats.naturalImportance = importance;
328         return importance;
329     }
330 
331     // copy any notes that the ranking system may have made before the update
copyRankingInformation(NotificationRecord previous)332     public void copyRankingInformation(NotificationRecord previous) {
333         mContactAffinity = previous.mContactAffinity;
334         mRecentlyIntrusive = previous.mRecentlyIntrusive;
335         mPackagePriority = previous.mPackagePriority;
336         mPackageVisibility = previous.mPackageVisibility;
337         mIntercept = previous.mIntercept;
338         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
339         mCreationTimeMs = previous.mCreationTimeMs;
340         mVisibleSinceMs = previous.mVisibleSinceMs;
341         if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
342             sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
343         }
344         // Don't copy importance information or mGlobalSortKey, recompute them.
345     }
346 
getNotification()347     public Notification getNotification() { return sbn.getNotification(); }
getFlags()348     public int getFlags() { return sbn.getNotification().flags; }
getUser()349     public UserHandle getUser() { return sbn.getUser(); }
getKey()350     public String getKey() { return sbn.getKey(); }
351     /** @deprecated Use {@link #getUser()} instead. */
getUserId()352     public int getUserId() { return sbn.getUserId(); }
353 
dump(ProtoOutputStream proto, boolean redact)354     void dump(ProtoOutputStream proto, boolean redact) {
355         proto.write(NotificationRecordProto.KEY, sbn.getKey());
356         if (getChannel() != null) {
357             proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
358         }
359         proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null);
360         proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null);
361         proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags);
362         proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey());
363         proto.write(NotificationRecordProto.IMPORTANCE, getImportance());
364         if (getSound() != null) {
365             proto.write(NotificationRecordProto.SOUND, getSound().toString());
366         }
367         if (getAudioAttributes() != null) {
368             proto.write(NotificationRecordProto.SOUND_USAGE, getAudioAttributes().getUsage());
369         }
370     }
371 
formatRemoteViews(RemoteViews rv)372     String formatRemoteViews(RemoteViews rv) {
373         if (rv == null) return "null";
374         return String.format("%s/0x%08x (%d bytes): %s",
375             rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString());
376     }
377 
dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)378     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
379         final Notification notification = sbn.getNotification();
380         final Icon icon = notification.getSmallIcon();
381         String iconStr = String.valueOf(icon);
382         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
383             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
384         }
385         pw.println(prefix + this);
386         prefix = prefix + "  ";
387         pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
388         pw.println(prefix + "icon=" + iconStr);
389         pw.println(prefix + "pri=" + notification.priority);
390         pw.println(prefix + "key=" + sbn.getKey());
391         pw.println(prefix + "seen=" + mIsSeen);
392         pw.println(prefix + "groupKey=" + getGroupKey());
393         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
394         pw.println(prefix + "contentIntent=" + notification.contentIntent);
395         pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
396 
397         pw.print(prefix + "tickerText=");
398         if (!TextUtils.isEmpty(notification.tickerText)) {
399             final String ticker = notification.tickerText.toString();
400             if (redact) {
401                 // if the string is long enough, we allow ourselves a few bytes for debugging
402                 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : "");
403                 pw.println("...");
404             } else {
405                 pw.println(ticker);
406             }
407         } else {
408             pw.println("null");
409         }
410         pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
411         pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
412         pw.println(prefix + "headsUpContentView="
413                 + formatRemoteViews(notification.headsUpContentView));
414         pw.print(prefix + String.format("color=0x%08x", notification.color));
415         pw.println(prefix + "timeout="
416                 + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
417         if (notification.actions != null && notification.actions.length > 0) {
418             pw.println(prefix + "actions={");
419             final int N = notification.actions.length;
420             for (int i = 0; i < N; i++) {
421                 final Notification.Action action = notification.actions[i];
422                 if (action != null) {
423                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
424                             prefix,
425                             i,
426                             action.title,
427                             action.actionIntent == null ? "null" : action.actionIntent.toString()
428                     ));
429                 }
430             }
431             pw.println(prefix + "  }");
432         }
433         if (notification.extras != null && notification.extras.size() > 0) {
434             pw.println(prefix + "extras={");
435             for (String key : notification.extras.keySet()) {
436                 pw.print(prefix + "    " + key + "=");
437                 Object val = notification.extras.get(key);
438                 if (val == null) {
439                     pw.println("null");
440                 } else {
441                     pw.print(val.getClass().getSimpleName());
442                     if (redact && (val instanceof CharSequence || val instanceof String)) {
443                         // redact contents from bugreports
444                     } else if (val instanceof Bitmap) {
445                         pw.print(String.format(" (%dx%d)",
446                                 ((Bitmap) val).getWidth(),
447                                 ((Bitmap) val).getHeight()));
448                     } else if (val.getClass().isArray()) {
449                         final int N = Array.getLength(val);
450                         pw.print(" (" + N + ")");
451                         if (!redact) {
452                             for (int j = 0; j < N; j++) {
453                                 pw.println();
454                                 pw.print(String.format("%s      [%d] %s",
455                                         prefix, j, String.valueOf(Array.get(val, j))));
456                             }
457                         }
458                     } else {
459                         pw.print(" (" + String.valueOf(val) + ")");
460                     }
461                     pw.println();
462                 }
463             }
464             pw.println(prefix + "}");
465         }
466         pw.println(prefix + "stats=" + stats.toString());
467         pw.println(prefix + "mContactAffinity=" + mContactAffinity);
468         pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
469         pw.println(prefix + "mPackagePriority=" + mPackagePriority);
470         pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
471         pw.println(prefix + "mUserImportance="
472                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
473         pw.println(prefix + "mImportance="
474                 + NotificationListenerService.Ranking.importanceToString(mImportance));
475         pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
476         pw.println(prefix + "mIntercept=" + mIntercept);
477         pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
478         pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
479         pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
480         pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
481         pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
482         pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
483         if (mPreChannelsNotification) {
484             pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
485                     notification.defaults, notification.flags));
486             pw.println(prefix + "n.sound=" + notification.sound);
487             pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
488             pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
489             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
490                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
491             pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
492         }
493         pw.println(prefix + "mSound= " + mSound);
494         pw.println(prefix + "mVibration= " + mVibration);
495         pw.println(prefix + "mAttributes= " + mAttributes);
496         pw.println(prefix + "mLight= " + mLight);
497         pw.println(prefix + "mShowBadge=" + mShowBadge);
498         pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
499         if (getPeopleOverride() != null) {
500             pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
501         }
502         if (getSnoozeCriteria() != null) {
503             pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
504         }
505     }
506 
507 
idDebugString(Context baseContext, String packageName, int id)508     static String idDebugString(Context baseContext, String packageName, int id) {
509         Context c;
510 
511         if (packageName != null) {
512             try {
513                 c = baseContext.createPackageContext(packageName, 0);
514             } catch (NameNotFoundException e) {
515                 c = baseContext;
516             }
517         } else {
518             c = baseContext;
519         }
520 
521         Resources r = c.getResources();
522         try {
523             return r.getResourceName(id);
524         } catch (Resources.NotFoundException e) {
525             return "<name unknown>";
526         }
527     }
528 
529     @Override
toString()530     public final String toString() {
531         return String.format(
532                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
533                         " channel=%s: %s)",
534                 System.identityHashCode(this),
535                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
536                 this.sbn.getTag(), this.mImportance, this.sbn.getKey(), this.getChannel().getId(),
537                 this.sbn.getNotification());
538     }
539 
setContactAffinity(float contactAffinity)540     public void setContactAffinity(float contactAffinity) {
541         mContactAffinity = contactAffinity;
542         if (mImportance < IMPORTANCE_DEFAULT &&
543                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
544             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
545         }
546     }
547 
getContactAffinity()548     public float getContactAffinity() {
549         return mContactAffinity;
550     }
551 
setRecentlyIntrusive(boolean recentlyIntrusive)552     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
553         mRecentlyIntrusive = recentlyIntrusive;
554         if (recentlyIntrusive) {
555             mLastIntrusive = System.currentTimeMillis();
556         }
557     }
558 
isRecentlyIntrusive()559     public boolean isRecentlyIntrusive() {
560         return mRecentlyIntrusive;
561     }
562 
getLastIntrusive()563     public long getLastIntrusive() {
564         return mLastIntrusive;
565     }
566 
setPackagePriority(int packagePriority)567     public void setPackagePriority(int packagePriority) {
568         mPackagePriority = packagePriority;
569     }
570 
getPackagePriority()571     public int getPackagePriority() {
572         return mPackagePriority;
573     }
574 
setPackageVisibilityOverride(int packageVisibility)575     public void setPackageVisibilityOverride(int packageVisibility) {
576         mPackageVisibility = packageVisibility;
577     }
578 
getPackageVisibilityOverride()579     public int getPackageVisibilityOverride() {
580         return mPackageVisibility;
581     }
582 
setUserImportance(int importance)583     public void setUserImportance(int importance) {
584         mUserImportance = importance;
585         applyUserImportance();
586     }
587 
getUserExplanation()588     private String getUserExplanation() {
589         if (mUserExplanation == null) {
590             mUserExplanation = mContext.getResources().getString(
591                     com.android.internal.R.string.importance_from_user);
592         }
593         return mUserExplanation;
594     }
595 
getPeopleExplanation()596     private String getPeopleExplanation() {
597         if (mPeopleExplanation == null) {
598             mPeopleExplanation = mContext.getResources().getString(
599                     com.android.internal.R.string.importance_from_person);
600         }
601         return mPeopleExplanation;
602     }
603 
applyUserImportance()604     private void applyUserImportance() {
605         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
606             mImportance = mUserImportance;
607             mImportanceExplanation = getUserExplanation();
608         }
609     }
610 
getUserImportance()611     public int getUserImportance() {
612         return mUserImportance;
613     }
614 
setImportance(int importance, CharSequence explanation)615     public void setImportance(int importance, CharSequence explanation) {
616         if (importance != IMPORTANCE_UNSPECIFIED) {
617             mImportance = importance;
618             mImportanceExplanation = explanation;
619         }
620         applyUserImportance();
621     }
622 
getImportance()623     public int getImportance() {
624         return mImportance;
625     }
626 
getImportanceExplanation()627     public CharSequence getImportanceExplanation() {
628         return mImportanceExplanation;
629     }
630 
setIntercepted(boolean intercept)631     public boolean setIntercepted(boolean intercept) {
632         mIntercept = intercept;
633         return mIntercept;
634     }
635 
isIntercepted()636     public boolean isIntercepted() {
637         return mIntercept;
638     }
639 
setSuppressedVisualEffects(int effects)640     public void setSuppressedVisualEffects(int effects) {
641         mSuppressedVisualEffects = effects;
642     }
643 
getSuppressedVisualEffects()644     public int getSuppressedVisualEffects() {
645         return mSuppressedVisualEffects;
646     }
647 
isCategory(String category)648     public boolean isCategory(String category) {
649         return Objects.equals(getNotification().category, category);
650     }
651 
isAudioStream(int stream)652     public boolean isAudioStream(int stream) {
653         return getNotification().audioStreamType == stream;
654     }
655 
isAudioAttributesUsage(int usage)656     public boolean isAudioAttributesUsage(int usage) {
657         final AudioAttributes attributes = getNotification().audioAttributes;
658         return attributes != null && attributes.getUsage() == usage;
659     }
660 
661     /**
662      * Returns the timestamp to use for time-based sorting in the ranker.
663      */
getRankingTimeMs()664     public long getRankingTimeMs() {
665         return mRankingTimeMs;
666     }
667 
668     /**
669      * @param now this current time in milliseconds.
670      * @returns the number of milliseconds since the most recent update, or the post time if none.
671      */
getFreshnessMs(long now)672     public int getFreshnessMs(long now) {
673         return (int) (now - mUpdateTimeMs);
674     }
675 
676     /**
677      * @param now this current time in milliseconds.
678      * @returns the number of milliseconds since the the first post, ignoring updates.
679      */
getLifespanMs(long now)680     public int getLifespanMs(long now) {
681         return (int) (now - mCreationTimeMs);
682     }
683 
684     /**
685      * @param now this current time in milliseconds.
686      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
687      */
getExposureMs(long now)688     public int getExposureMs(long now) {
689         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
690     }
691 
692     /**
693      * Set the visibility of the notification.
694      */
setVisibility(boolean visible, int rank)695     public void setVisibility(boolean visible, int rank) {
696         final long now = System.currentTimeMillis();
697         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
698         stats.onVisibilityChanged(visible);
699         MetricsLogger.action(getLogMaker(now)
700                 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
701                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
702                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
703         if (visible) {
704             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
705         }
706         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
707                 getLifespanMs(now),
708                 getFreshnessMs(now),
709                 0, // exposure time
710                 rank);
711     }
712 
713     /**
714      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
715      *     of the previous notification record, 0 otherwise
716      */
calculateRankingTimeMs(long previousRankingTimeMs)717     private long calculateRankingTimeMs(long previousRankingTimeMs) {
718         Notification n = getNotification();
719         // Take developer provided 'when', unless it's in the future.
720         if (n.when != 0 && n.when <= sbn.getPostTime()) {
721             return n.when;
722         }
723         // If we've ranked a previous instance with a timestamp, inherit it. This case is
724         // important in order to have ranking stability for updating notifications.
725         if (previousRankingTimeMs > 0) {
726             return previousRankingTimeMs;
727         }
728         return sbn.getPostTime();
729     }
730 
setGlobalSortKey(String globalSortKey)731     public void setGlobalSortKey(String globalSortKey) {
732         mGlobalSortKey = globalSortKey;
733     }
734 
getGlobalSortKey()735     public String getGlobalSortKey() {
736         return mGlobalSortKey;
737     }
738 
739     /** Check if any of the listeners have marked this notification as seen by the user. */
isSeen()740     public boolean isSeen() {
741         return mIsSeen;
742     }
743 
744     /** Mark the notification as seen by the user. */
setSeen()745     public void setSeen() {
746         mIsSeen = true;
747     }
748 
setAuthoritativeRank(int authoritativeRank)749     public void setAuthoritativeRank(int authoritativeRank) {
750         mAuthoritativeRank = authoritativeRank;
751     }
752 
getAuthoritativeRank()753     public int getAuthoritativeRank() {
754         return mAuthoritativeRank;
755     }
756 
getGroupKey()757     public String getGroupKey() {
758         return sbn.getGroupKey();
759     }
760 
setOverrideGroupKey(String overrideGroupKey)761     public void setOverrideGroupKey(String overrideGroupKey) {
762         sbn.setOverrideGroupKey(overrideGroupKey);
763         mGroupLogTag = null;
764     }
765 
getGroupLogTag()766     private String getGroupLogTag() {
767         if (mGroupLogTag == null) {
768             mGroupLogTag = shortenTag(sbn.getGroup());
769         }
770         return mGroupLogTag;
771     }
772 
getChannelIdLogTag()773     private String getChannelIdLogTag() {
774         if (mChannelIdLogTag == null) {
775             mChannelIdLogTag = shortenTag(mChannel.getId());
776         }
777         return mChannelIdLogTag;
778     }
779 
shortenTag(String longTag)780     private String shortenTag(String longTag) {
781         if (longTag == null) {
782             return null;
783         }
784         if (longTag.length() < MAX_LOGTAG_LENGTH) {
785             return longTag;
786         } else {
787             return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
788                     Integer.toHexString(longTag.hashCode());
789         }
790     }
791 
isImportanceFromUser()792     public boolean isImportanceFromUser() {
793         return mImportance == mUserImportance;
794     }
795 
getChannel()796     public NotificationChannel getChannel() {
797         return mChannel;
798     }
799 
updateNotificationChannel(NotificationChannel channel)800     protected void updateNotificationChannel(NotificationChannel channel) {
801         if (channel != null) {
802             mChannel = channel;
803             calculateImportance();
804         }
805     }
806 
setShowBadge(boolean showBadge)807     public void setShowBadge(boolean showBadge) {
808         mShowBadge = showBadge;
809     }
810 
canShowBadge()811     public boolean canShowBadge() {
812         return mShowBadge;
813     }
814 
getLight()815     public Light getLight() {
816         return mLight;
817     }
818 
getSound()819     public Uri getSound() {
820         return mSound;
821     }
822 
getVibration()823     public long[] getVibration() {
824         return mVibration;
825     }
826 
getAudioAttributes()827     public AudioAttributes getAudioAttributes() {
828         return mAttributes;
829     }
830 
getPeopleOverride()831     public ArrayList<String> getPeopleOverride() {
832         return mPeopleOverride;
833     }
834 
setPeopleOverride(ArrayList<String> people)835     protected void setPeopleOverride(ArrayList<String> people) {
836         mPeopleOverride = people;
837     }
838 
getSnoozeCriteria()839     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
840         return mSnoozeCriteria;
841     }
842 
setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria)843     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
844         mSnoozeCriteria = snoozeCriteria;
845     }
846 
getLogMaker(long now)847     public LogMaker getLogMaker(long now) {
848         if (mLogMaker == null) {
849             // initialize fields that only change on update (so a new record)
850             mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
851                     .setPackageName(sbn.getPackageName())
852                     .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
853                     .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
854                     .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
855         }
856         // reset fields that can change between updates, or are used by multiple logs
857         return mLogMaker
858                 .clearCategory()
859                 .clearType()
860                 .clearSubtype()
861                 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
862                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
863                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
864                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
865                         sbn.getNotification().isGroupSummary() ? 1 : 0)
866                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
867                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
868                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
869     }
870 
getLogMaker()871     public LogMaker getLogMaker() {
872         return getLogMaker(System.currentTimeMillis());
873     }
874 
875     @VisibleForTesting
876     static final class Light {
877         public final int color;
878         public final int onMs;
879         public final int offMs;
880 
Light(int color, int onMs, int offMs)881         public Light(int color, int onMs, int offMs) {
882             this.color = color;
883             this.onMs = onMs;
884             this.offMs = offMs;
885         }
886 
887         @Override
equals(Object o)888         public boolean equals(Object o) {
889             if (this == o) return true;
890             if (o == null || getClass() != o.getClass()) return false;
891 
892             Light light = (Light) o;
893 
894             if (color != light.color) return false;
895             if (onMs != light.onMs) return false;
896             return offMs == light.offMs;
897 
898         }
899 
900         @Override
hashCode()901         public int hashCode() {
902             int result = color;
903             result = 31 * result + onMs;
904             result = 31 * result + offMs;
905             return result;
906         }
907 
908         @Override
toString()909         public String toString() {
910             return "Light{" +
911                     "color=" + color +
912                     ", onMs=" + onMs +
913                     ", offMs=" + offMs +
914                     '}';
915         }
916     }
917 }
918