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.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
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    353     public Notification getNotification() { return sbn.getNotification(); }
    354     public int getFlags() { return sbn.getNotification().flags; }
    355     public UserHandle getUser() { return sbn.getUser(); }
    356     public String getKey() { return sbn.getKey(); }
    357     /** @deprecated Use {@link #getUser()} instead. */
    358     public int getUserId() { return sbn.getUserId(); }
    359 
    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 
    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 
    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 
    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
    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 
    549     public void addAdjustment(Adjustment adjustment) {
    550         synchronized (mAdjustments) {
    551             mAdjustments.add(adjustment);
    552         }
    553     }
    554 
    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 
    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 
    587     public float getContactAffinity() {
    588         return mContactAffinity;
    589     }
    590 
    591     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
    592         mRecentlyIntrusive = recentlyIntrusive;
    593         if (recentlyIntrusive) {
    594             mLastIntrusive = System.currentTimeMillis();
    595         }
    596     }
    597 
    598     public boolean isRecentlyIntrusive() {
    599         return mRecentlyIntrusive;
    600     }
    601 
    602     public long getLastIntrusive() {
    603         return mLastIntrusive;
    604     }
    605 
    606     public void setPackagePriority(int packagePriority) {
    607         mPackagePriority = packagePriority;
    608     }
    609 
    610     public int getPackagePriority() {
    611         return mPackagePriority;
    612     }
    613 
    614     public void setPackageVisibilityOverride(int packageVisibility) {
    615         mPackageVisibility = packageVisibility;
    616     }
    617 
    618     public int getPackageVisibilityOverride() {
    619         return mPackageVisibility;
    620     }
    621 
    622     public void setUserImportance(int importance) {
    623         mUserImportance = importance;
    624         applyUserImportance();
    625     }
    626 
    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 
    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 
    643     private void applyUserImportance() {
    644         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
    645             mImportance = mUserImportance;
    646             mImportanceExplanation = getUserExplanation();
    647         }
    648     }
    649 
    650     public int getUserImportance() {
    651         return mUserImportance;
    652     }
    653 
    654     public void setImportance(int importance, CharSequence explanation) {
    655         if (importance != IMPORTANCE_UNSPECIFIED) {
    656             mImportance = importance;
    657             mImportanceExplanation = explanation;
    658         }
    659         applyUserImportance();
    660     }
    661 
    662     public int getImportance() {
    663         return mImportance;
    664     }
    665 
    666     public CharSequence getImportanceExplanation() {
    667         return mImportanceExplanation;
    668     }
    669 
    670     public boolean setIntercepted(boolean intercept) {
    671         mIntercept = intercept;
    672         return mIntercept;
    673     }
    674 
    675     public boolean isIntercepted() {
    676         return mIntercept;
    677     }
    678 
    679     public void setSuppressedVisualEffects(int effects) {
    680         mSuppressedVisualEffects = effects;
    681     }
    682 
    683     public int getSuppressedVisualEffects() {
    684         return mSuppressedVisualEffects;
    685     }
    686 
    687     public boolean isCategory(String category) {
    688         return Objects.equals(getNotification().category, category);
    689     }
    690 
    691     public boolean isAudioStream(int stream) {
    692         return getNotification().audioStreamType == stream;
    693     }
    694 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    770     public void setGlobalSortKey(String globalSortKey) {
    771         mGlobalSortKey = globalSortKey;
    772     }
    773 
    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. */
    779     public boolean isSeen() {
    780         return mIsSeen;
    781     }
    782 
    783     /** Mark the notification as seen by the user. */
    784     public void setSeen() {
    785         mIsSeen = true;
    786     }
    787 
    788     public void setAuthoritativeRank(int authoritativeRank) {
    789         mAuthoritativeRank = authoritativeRank;
    790     }
    791 
    792     public int getAuthoritativeRank() {
    793         return mAuthoritativeRank;
    794     }
    795 
    796     public String getGroupKey() {
    797         return sbn.getGroupKey();
    798     }
    799 
    800     public void setOverrideGroupKey(String overrideGroupKey) {
    801         sbn.setOverrideGroupKey(overrideGroupKey);
    802         mGroupLogTag = null;
    803     }
    804 
    805     private String getGroupLogTag() {
    806         if (mGroupLogTag == null) {
    807             mGroupLogTag = shortenTag(sbn.getGroup());
    808         }
    809         return mGroupLogTag;
    810     }
    811 
    812     private String getChannelIdLogTag() {
    813         if (mChannelIdLogTag == null) {
    814             mChannelIdLogTag = shortenTag(mChannel.getId());
    815         }
    816         return mChannelIdLogTag;
    817     }
    818 
    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 
    831     public boolean isImportanceFromUser() {
    832         return mImportance == mUserImportance;
    833     }
    834 
    835     public NotificationChannel getChannel() {
    836         return mChannel;
    837     }
    838 
    839     protected void updateNotificationChannel(NotificationChannel channel) {
    840         if (channel != null) {
    841             mChannel = channel;
    842             calculateImportance();
    843         }
    844     }
    845 
    846     public void setShowBadge(boolean showBadge) {
    847         mShowBadge = showBadge;
    848     }
    849 
    850     public boolean canShowBadge() {
    851         return mShowBadge;
    852     }
    853 
    854     public Light getLight() {
    855         return mLight;
    856     }
    857 
    858     public Uri getSound() {
    859         return mSound;
    860     }
    861 
    862     public long[] getVibration() {
    863         return mVibration;
    864     }
    865 
    866     public AudioAttributes getAudioAttributes() {
    867         return mAttributes;
    868     }
    869 
    870     public ArrayList<String> getPeopleOverride() {
    871         return mPeopleOverride;
    872     }
    873 
    874     protected void setPeopleOverride(ArrayList<String> people) {
    875         mPeopleOverride = people;
    876     }
    877 
    878     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
    879         return mSnoozeCriteria;
    880     }
    881 
    882     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
    883         mSnoozeCriteria = snoozeCriteria;
    884     }
    885 
    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 
    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 
    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
    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
    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
    948         public String toString() {
    949             return "Light{" +
    950                     "color=" + color +
    951                     ", onMs=" + onMs +
    952                     ", offMs=" + offMs +
    953                     '}';
    954         }
    955     }
    956 }
    957