OSDN Git Service

Merge "Allow setup apps to colorize notifications." into oc-dr1-dev
[android-x86/frameworks-base.git] / services / core / java / com / android / server / notification / NotificationRecord.java
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
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
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
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
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
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
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
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
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
347     public Notification getNotification() { return sbn.getNotification(); }
348     public int getFlags() { return sbn.getNotification().flags; }
349     public UserHandle getUser() { return sbn.getUser(); }
350     public String getKey() { return sbn.getKey(); }
351     /** @deprecated Use {@link #getUser()} instead. */
352     public int getUserId() { return sbn.getUserId(); }
353
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
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
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 + "flags=0x" + Integer.toHexString(notification.flags));
390         pw.println(prefix + "pri=" + notification.priority);
391         pw.println(prefix + "key=" + sbn.getKey());
392         pw.println(prefix + "seen=" + mIsSeen);
393         pw.println(prefix + "groupKey=" + getGroupKey());
394         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
395         pw.println(prefix + "contentIntent=" + notification.contentIntent);
396         pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
397
398         pw.print(prefix + "tickerText=");
399         if (!TextUtils.isEmpty(notification.tickerText)) {
400             final String ticker = notification.tickerText.toString();
401             if (redact) {
402                 // if the string is long enough, we allow ourselves a few bytes for debugging
403                 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : "");
404                 pw.println("...");
405             } else {
406                 pw.println(ticker);
407             }
408         } else {
409             pw.println("null");
410         }
411         pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
412         pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
413         pw.println(prefix + "headsUpContentView="
414                 + formatRemoteViews(notification.headsUpContentView));
415         pw.print(prefix + String.format("color=0x%08x", notification.color));
416         pw.println(prefix + "timeout="
417                 + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
418         if (notification.actions != null && notification.actions.length > 0) {
419             pw.println(prefix + "actions={");
420             final int N = notification.actions.length;
421             for (int i = 0; i < N; i++) {
422                 final Notification.Action action = notification.actions[i];
423                 if (action != null) {
424                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
425                             prefix,
426                             i,
427                             action.title,
428                             action.actionIntent == null ? "null" : action.actionIntent.toString()
429                     ));
430                 }
431             }
432             pw.println(prefix + "  }");
433         }
434         if (notification.extras != null && notification.extras.size() > 0) {
435             pw.println(prefix + "extras={");
436             for (String key : notification.extras.keySet()) {
437                 pw.print(prefix + "    " + key + "=");
438                 Object val = notification.extras.get(key);
439                 if (val == null) {
440                     pw.println("null");
441                 } else {
442                     pw.print(val.getClass().getSimpleName());
443                     if (redact && (val instanceof CharSequence || val instanceof String)) {
444                         // redact contents from bugreports
445                     } else if (val instanceof Bitmap) {
446                         pw.print(String.format(" (%dx%d)",
447                                 ((Bitmap) val).getWidth(),
448                                 ((Bitmap) val).getHeight()));
449                     } else if (val.getClass().isArray()) {
450                         final int N = Array.getLength(val);
451                         pw.print(" (" + N + ")");
452                         if (!redact) {
453                             for (int j = 0; j < N; j++) {
454                                 pw.println();
455                                 pw.print(String.format("%s      [%d] %s",
456                                         prefix, j, String.valueOf(Array.get(val, j))));
457                             }
458                         }
459                     } else {
460                         pw.print(" (" + String.valueOf(val) + ")");
461                     }
462                     pw.println();
463                 }
464             }
465             pw.println(prefix + "}");
466         }
467         pw.println(prefix + "stats=" + stats.toString());
468         pw.println(prefix + "mContactAffinity=" + mContactAffinity);
469         pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
470         pw.println(prefix + "mPackagePriority=" + mPackagePriority);
471         pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
472         pw.println(prefix + "mUserImportance="
473                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
474         pw.println(prefix + "mImportance="
475                 + NotificationListenerService.Ranking.importanceToString(mImportance));
476         pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
477         pw.println(prefix + "mIntercept=" + mIntercept);
478         pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
479         pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
480         pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
481         pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
482         pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
483         pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
484         if (mPreChannelsNotification) {
485             pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
486                     notification.defaults, notification.flags));
487             pw.println(prefix + "n.sound=" + notification.sound);
488             pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
489             pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
490             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
491                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
492             pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
493         }
494         pw.println(prefix + "mSound= " + mSound);
495         pw.println(prefix + "mVibration= " + mVibration);
496         pw.println(prefix + "mAttributes= " + mAttributes);
497         pw.println(prefix + "mLight= " + mLight);
498         pw.println(prefix + "mShowBadge=" + mShowBadge);
499         pw.println(prefix + "mColorized=" + notification.isColorized());
500         pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
501         if (getPeopleOverride() != null) {
502             pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
503         }
504         if (getSnoozeCriteria() != null) {
505             pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
506         }
507     }
508
509
510     static String idDebugString(Context baseContext, String packageName, int id) {
511         Context c;
512
513         if (packageName != null) {
514             try {
515                 c = baseContext.createPackageContext(packageName, 0);
516             } catch (NameNotFoundException e) {
517                 c = baseContext;
518             }
519         } else {
520             c = baseContext;
521         }
522
523         Resources r = c.getResources();
524         try {
525             return r.getResourceName(id);
526         } catch (Resources.NotFoundException e) {
527             return "<name unknown>";
528         }
529     }
530
531     @Override
532     public final String toString() {
533         return String.format(
534                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
535                         ": %s)",
536                 System.identityHashCode(this),
537                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
538                 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
539                 this.sbn.getNotification());
540     }
541
542     public void setContactAffinity(float contactAffinity) {
543         mContactAffinity = contactAffinity;
544         if (mImportance < IMPORTANCE_DEFAULT &&
545                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
546             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
547         }
548     }
549
550     public float getContactAffinity() {
551         return mContactAffinity;
552     }
553
554     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
555         mRecentlyIntrusive = recentlyIntrusive;
556         if (recentlyIntrusive) {
557             mLastIntrusive = System.currentTimeMillis();
558         }
559     }
560
561     public boolean isRecentlyIntrusive() {
562         return mRecentlyIntrusive;
563     }
564
565     public long getLastIntrusive() {
566         return mLastIntrusive;
567     }
568
569     public void setPackagePriority(int packagePriority) {
570         mPackagePriority = packagePriority;
571     }
572
573     public int getPackagePriority() {
574         return mPackagePriority;
575     }
576
577     public void setPackageVisibilityOverride(int packageVisibility) {
578         mPackageVisibility = packageVisibility;
579     }
580
581     public int getPackageVisibilityOverride() {
582         return mPackageVisibility;
583     }
584
585     public void setUserImportance(int importance) {
586         mUserImportance = importance;
587         applyUserImportance();
588     }
589
590     private String getUserExplanation() {
591         if (mUserExplanation == null) {
592             mUserExplanation = mContext.getResources().getString(
593                     com.android.internal.R.string.importance_from_user);
594         }
595         return mUserExplanation;
596     }
597
598     private String getPeopleExplanation() {
599         if (mPeopleExplanation == null) {
600             mPeopleExplanation = mContext.getResources().getString(
601                     com.android.internal.R.string.importance_from_person);
602         }
603         return mPeopleExplanation;
604     }
605
606     private void applyUserImportance() {
607         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
608             mImportance = mUserImportance;
609             mImportanceExplanation = getUserExplanation();
610         }
611     }
612
613     public int getUserImportance() {
614         return mUserImportance;
615     }
616
617     public void setImportance(int importance, CharSequence explanation) {
618         if (importance != IMPORTANCE_UNSPECIFIED) {
619             mImportance = importance;
620             mImportanceExplanation = explanation;
621         }
622         applyUserImportance();
623     }
624
625     public int getImportance() {
626         return mImportance;
627     }
628
629     public CharSequence getImportanceExplanation() {
630         return mImportanceExplanation;
631     }
632
633     public boolean setIntercepted(boolean intercept) {
634         mIntercept = intercept;
635         return mIntercept;
636     }
637
638     public boolean isIntercepted() {
639         return mIntercept;
640     }
641
642     public void setSuppressedVisualEffects(int effects) {
643         mSuppressedVisualEffects = effects;
644     }
645
646     public int getSuppressedVisualEffects() {
647         return mSuppressedVisualEffects;
648     }
649
650     public boolean isCategory(String category) {
651         return Objects.equals(getNotification().category, category);
652     }
653
654     public boolean isAudioStream(int stream) {
655         return getNotification().audioStreamType == stream;
656     }
657
658     public boolean isAudioAttributesUsage(int usage) {
659         final AudioAttributes attributes = getNotification().audioAttributes;
660         return attributes != null && attributes.getUsage() == usage;
661     }
662
663     /**
664      * Returns the timestamp to use for time-based sorting in the ranker.
665      */
666     public long getRankingTimeMs() {
667         return mRankingTimeMs;
668     }
669
670     /**
671      * @param now this current time in milliseconds.
672      * @returns the number of milliseconds since the most recent update, or the post time if none.
673      */
674     public int getFreshnessMs(long now) {
675         return (int) (now - mUpdateTimeMs);
676     }
677
678     /**
679      * @param now this current time in milliseconds.
680      * @returns the number of milliseconds since the the first post, ignoring updates.
681      */
682     public int getLifespanMs(long now) {
683         return (int) (now - mCreationTimeMs);
684     }
685
686     /**
687      * @param now this current time in milliseconds.
688      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
689      */
690     public int getExposureMs(long now) {
691         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
692     }
693
694     /**
695      * Set the visibility of the notification.
696      */
697     public void setVisibility(boolean visible, int rank) {
698         final long now = System.currentTimeMillis();
699         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
700         stats.onVisibilityChanged(visible);
701         MetricsLogger.action(getLogMaker(now)
702                 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
703                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
704                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
705         if (visible) {
706             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
707         }
708         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
709                 getLifespanMs(now),
710                 getFreshnessMs(now),
711                 0, // exposure time
712                 rank);
713     }
714
715     /**
716      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
717      *     of the previous notification record, 0 otherwise
718      */
719     private long calculateRankingTimeMs(long previousRankingTimeMs) {
720         Notification n = getNotification();
721         // Take developer provided 'when', unless it's in the future.
722         if (n.when != 0 && n.when <= sbn.getPostTime()) {
723             return n.when;
724         }
725         // If we've ranked a previous instance with a timestamp, inherit it. This case is
726         // important in order to have ranking stability for updating notifications.
727         if (previousRankingTimeMs > 0) {
728             return previousRankingTimeMs;
729         }
730         return sbn.getPostTime();
731     }
732
733     public void setGlobalSortKey(String globalSortKey) {
734         mGlobalSortKey = globalSortKey;
735     }
736
737     public String getGlobalSortKey() {
738         return mGlobalSortKey;
739     }
740
741     /** Check if any of the listeners have marked this notification as seen by the user. */
742     public boolean isSeen() {
743         return mIsSeen;
744     }
745
746     /** Mark the notification as seen by the user. */
747     public void setSeen() {
748         mIsSeen = true;
749     }
750
751     public void setAuthoritativeRank(int authoritativeRank) {
752         mAuthoritativeRank = authoritativeRank;
753     }
754
755     public int getAuthoritativeRank() {
756         return mAuthoritativeRank;
757     }
758
759     public String getGroupKey() {
760         return sbn.getGroupKey();
761     }
762
763     public void setOverrideGroupKey(String overrideGroupKey) {
764         sbn.setOverrideGroupKey(overrideGroupKey);
765         mGroupLogTag = null;
766     }
767
768     private String getGroupLogTag() {
769         if (mGroupLogTag == null) {
770             mGroupLogTag = shortenTag(sbn.getGroup());
771         }
772         return mGroupLogTag;
773     }
774
775     private String getChannelIdLogTag() {
776         if (mChannelIdLogTag == null) {
777             mChannelIdLogTag = shortenTag(mChannel.getId());
778         }
779         return mChannelIdLogTag;
780     }
781
782     private String shortenTag(String longTag) {
783         if (longTag == null) {
784             return null;
785         }
786         if (longTag.length() < MAX_LOGTAG_LENGTH) {
787             return longTag;
788         } else {
789             return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
790                     Integer.toHexString(longTag.hashCode());
791         }
792     }
793
794     public boolean isImportanceFromUser() {
795         return mImportance == mUserImportance;
796     }
797
798     public NotificationChannel getChannel() {
799         return mChannel;
800     }
801
802     protected void updateNotificationChannel(NotificationChannel channel) {
803         if (channel != null) {
804             mChannel = channel;
805             calculateImportance();
806         }
807     }
808
809     public void setShowBadge(boolean showBadge) {
810         mShowBadge = showBadge;
811     }
812
813     public boolean canShowBadge() {
814         return mShowBadge;
815     }
816
817     public Light getLight() {
818         return mLight;
819     }
820
821     public Uri getSound() {
822         return mSound;
823     }
824
825     public long[] getVibration() {
826         return mVibration;
827     }
828
829     public AudioAttributes getAudioAttributes() {
830         return mAttributes;
831     }
832
833     public ArrayList<String> getPeopleOverride() {
834         return mPeopleOverride;
835     }
836
837     protected void setPeopleOverride(ArrayList<String> people) {
838         mPeopleOverride = people;
839     }
840
841     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
842         return mSnoozeCriteria;
843     }
844
845     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
846         mSnoozeCriteria = snoozeCriteria;
847     }
848
849     public LogMaker getLogMaker(long now) {
850         if (mLogMaker == null) {
851             // initialize fields that only change on update (so a new record)
852             mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
853                     .setPackageName(sbn.getPackageName())
854                     .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
855                     .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
856                     .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
857         }
858         // reset fields that can change between updates, or are used by multiple logs
859         return mLogMaker
860                 .clearCategory()
861                 .clearType()
862                 .clearSubtype()
863                 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
864                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
865                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
866                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
867                         sbn.getNotification().isGroupSummary() ? 1 : 0)
868                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
869                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
870                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
871     }
872
873     public LogMaker getLogMaker() {
874         return getLogMaker(System.currentTimeMillis());
875     }
876
877     @VisibleForTesting
878     static final class Light {
879         public final int color;
880         public final int onMs;
881         public final int offMs;
882
883         public Light(int color, int onMs, int offMs) {
884             this.color = color;
885             this.onMs = onMs;
886             this.offMs = offMs;
887         }
888
889         @Override
890         public boolean equals(Object o) {
891             if (this == o) return true;
892             if (o == null || getClass() != o.getClass()) return false;
893
894             Light light = (Light) o;
895
896             if (color != light.color) return false;
897             if (onMs != light.onMs) return false;
898             return offMs == light.offMs;
899
900         }
901
902         @Override
903         public int hashCode() {
904             int result = color;
905             result = 31 * result + onMs;
906             result = 31 * result + offMs;
907             return result;
908         }
909
910         @Override
911         public String toString() {
912             return "Light{" +
913                     "color=" + color +
914                     ", onMs=" + onMs +
915                     ", offMs=" + offMs +
916                     '}';
917         }
918     }
919 }