Home | History | Annotate | Download | only in notification
      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 + "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 
    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
    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 
    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 
    548     public float getContactAffinity() {
    549         return mContactAffinity;
    550     }
    551 
    552     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
    553         mRecentlyIntrusive = recentlyIntrusive;
    554         if (recentlyIntrusive) {
    555             mLastIntrusive = System.currentTimeMillis();
    556         }
    557     }
    558 
    559     public boolean isRecentlyIntrusive() {
    560         return mRecentlyIntrusive;
    561     }
    562 
    563     public long getLastIntrusive() {
    564         return mLastIntrusive;
    565     }
    566 
    567     public void setPackagePriority(int packagePriority) {
    568         mPackagePriority = packagePriority;
    569     }
    570 
    571     public int getPackagePriority() {
    572         return mPackagePriority;
    573     }
    574 
    575     public void setPackageVisibilityOverride(int packageVisibility) {
    576         mPackageVisibility = packageVisibility;
    577     }
    578 
    579     public int getPackageVisibilityOverride() {
    580         return mPackageVisibility;
    581     }
    582 
    583     public void setUserImportance(int importance) {
    584         mUserImportance = importance;
    585         applyUserImportance();
    586     }
    587 
    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 
    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 
    604     private void applyUserImportance() {
    605         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
    606             mImportance = mUserImportance;
    607             mImportanceExplanation = getUserExplanation();
    608         }
    609     }
    610 
    611     public int getUserImportance() {
    612         return mUserImportance;
    613     }
    614 
    615     public void setImportance(int importance, CharSequence explanation) {
    616         if (importance != IMPORTANCE_UNSPECIFIED) {
    617             mImportance = importance;
    618             mImportanceExplanation = explanation;
    619         }
    620         applyUserImportance();
    621     }
    622 
    623     public int getImportance() {
    624         return mImportance;
    625     }
    626 
    627     public CharSequence getImportanceExplanation() {
    628         return mImportanceExplanation;
    629     }
    630 
    631     public boolean setIntercepted(boolean intercept) {
    632         mIntercept = intercept;
    633         return mIntercept;
    634     }
    635 
    636     public boolean isIntercepted() {
    637         return mIntercept;
    638     }
    639 
    640     public void setSuppressedVisualEffects(int effects) {
    641         mSuppressedVisualEffects = effects;
    642     }
    643 
    644     public int getSuppressedVisualEffects() {
    645         return mSuppressedVisualEffects;
    646     }
    647 
    648     public boolean isCategory(String category) {
    649         return Objects.equals(getNotification().category, category);
    650     }
    651 
    652     public boolean isAudioStream(int stream) {
    653         return getNotification().audioStreamType == stream;
    654     }
    655 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    731     public void setGlobalSortKey(String globalSortKey) {
    732         mGlobalSortKey = globalSortKey;
    733     }
    734 
    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. */
    740     public boolean isSeen() {
    741         return mIsSeen;
    742     }
    743 
    744     /** Mark the notification as seen by the user. */
    745     public void setSeen() {
    746         mIsSeen = true;
    747     }
    748 
    749     public void setAuthoritativeRank(int authoritativeRank) {
    750         mAuthoritativeRank = authoritativeRank;
    751     }
    752 
    753     public int getAuthoritativeRank() {
    754         return mAuthoritativeRank;
    755     }
    756 
    757     public String getGroupKey() {
    758         return sbn.getGroupKey();
    759     }
    760 
    761     public void setOverrideGroupKey(String overrideGroupKey) {
    762         sbn.setOverrideGroupKey(overrideGroupKey);
    763         mGroupLogTag = null;
    764     }
    765 
    766     private String getGroupLogTag() {
    767         if (mGroupLogTag == null) {
    768             mGroupLogTag = shortenTag(sbn.getGroup());
    769         }
    770         return mGroupLogTag;
    771     }
    772 
    773     private String getChannelIdLogTag() {
    774         if (mChannelIdLogTag == null) {
    775             mChannelIdLogTag = shortenTag(mChannel.getId());
    776         }
    777         return mChannelIdLogTag;
    778     }
    779 
    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 
    792     public boolean isImportanceFromUser() {
    793         return mImportance == mUserImportance;
    794     }
    795 
    796     public NotificationChannel getChannel() {
    797         return mChannel;
    798     }
    799 
    800     protected void updateNotificationChannel(NotificationChannel channel) {
    801         if (channel != null) {
    802             mChannel = channel;
    803             calculateImportance();
    804         }
    805     }
    806 
    807     public void setShowBadge(boolean showBadge) {
    808         mShowBadge = showBadge;
    809     }
    810 
    811     public boolean canShowBadge() {
    812         return mShowBadge;
    813     }
    814 
    815     public Light getLight() {
    816         return mLight;
    817     }
    818 
    819     public Uri getSound() {
    820         return mSound;
    821     }
    822 
    823     public long[] getVibration() {
    824         return mVibration;
    825     }
    826 
    827     public AudioAttributes getAudioAttributes() {
    828         return mAttributes;
    829     }
    830 
    831     public ArrayList<String> getPeopleOverride() {
    832         return mPeopleOverride;
    833     }
    834 
    835     protected void setPeopleOverride(ArrayList<String> people) {
    836         mPeopleOverride = people;
    837     }
    838 
    839     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
    840         return mSnoozeCriteria;
    841     }
    842 
    843     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
    844         mSnoozeCriteria = snoozeCriteria;
    845     }
    846 
    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 
    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 
    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
    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
    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
    909         public String toString() {
    910             return "Light{" +
    911                     "color=" + color +
    912                     ", onMs=" + onMs +
    913                     ", offMs=" + offMs +
    914                     '}';
    915         }
    916     }
    917 }
    918