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