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.NotificationChannel.USER_LOCKED_IMPORTANCE;
     19 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
     20 import static android.app.NotificationManager.IMPORTANCE_HIGH;
     21 import static android.app.NotificationManager.IMPORTANCE_LOW;
     22 import static android.app.NotificationManager.IMPORTANCE_MIN;
     23 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
     24 import static android.service.notification.NotificationListenerService.Ranking
     25         .USER_SENTIMENT_NEUTRAL;
     26 import static android.service.notification.NotificationListenerService.Ranking
     27         .USER_SENTIMENT_POSITIVE;
     28 
     29 import android.annotation.Nullable;
     30 import android.app.ActivityManager;
     31 import android.app.IActivityManager;
     32 import android.app.Notification;
     33 import android.app.NotificationChannel;
     34 import android.content.ContentProvider;
     35 import android.content.ContentResolver;
     36 import android.content.Context;
     37 import android.content.Intent;
     38 import android.content.pm.PackageManager;
     39 import android.content.pm.PackageManager.NameNotFoundException;
     40 import android.content.pm.PackageManagerInternal;
     41 import android.content.res.Resources;
     42 import android.graphics.Bitmap;
     43 import android.graphics.drawable.Icon;
     44 import android.media.AudioAttributes;
     45 import android.media.AudioSystem;
     46 import android.metrics.LogMaker;
     47 import android.net.Uri;
     48 import android.os.Binder;
     49 import android.os.Build;
     50 import android.os.Bundle;
     51 import android.os.IBinder;
     52 import android.os.RemoteException;
     53 import android.os.UserHandle;
     54 import android.provider.Settings;
     55 import android.service.notification.Adjustment;
     56 import android.service.notification.NotificationListenerService;
     57 import android.service.notification.NotificationRecordProto;
     58 import android.service.notification.NotificationStats;
     59 import android.service.notification.SnoozeCriterion;
     60 import android.service.notification.StatusBarNotification;
     61 import android.text.TextUtils;
     62 import android.util.ArraySet;
     63 import android.util.Log;
     64 import android.util.TimeUtils;
     65 import android.util.proto.ProtoOutputStream;
     66 import android.widget.RemoteViews;
     67 
     68 import com.android.internal.annotations.VisibleForTesting;
     69 import com.android.internal.logging.MetricsLogger;
     70 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     71 import com.android.server.EventLogTags;
     72 import com.android.server.LocalServices;
     73 
     74 import java.io.PrintWriter;
     75 import java.lang.reflect.Array;
     76 import java.util.ArrayList;
     77 import java.util.Arrays;
     78 import java.util.List;
     79 import java.util.Objects;
     80 
     81 /**
     82  * Holds data about notifications that should not be shared with the
     83  * {@link android.service.notification.NotificationListenerService}s.
     84  *
     85  * <p>These objects should not be mutated unless the code is synchronized
     86  * on {@link NotificationManagerService#mNotificationLock}, and any
     87  * modification should be followed by a sorting of that list.</p>
     88  *
     89  * <p>Is sortable by {@link NotificationComparator}.</p>
     90  *
     91  * {@hide}
     92  */
     93 public final class NotificationRecord {
     94     static final String TAG = "NotificationRecord";
     95     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     96     private static final int MAX_LOGTAG_LENGTH = 35;
     97     final StatusBarNotification sbn;
     98     IActivityManager mAm;
     99     final int mTargetSdkVersion;
    100     final int mOriginalFlags;
    101     private final Context mContext;
    102 
    103     NotificationUsageStats.SingleNotificationStats stats;
    104     boolean isCanceled;
    105     IBinder permissionOwner;
    106 
    107     // These members are used by NotificationSignalExtractors
    108     // to communicate with the ranking module.
    109     private float mContactAffinity;
    110     private boolean mRecentlyIntrusive;
    111     private long mLastIntrusive;
    112 
    113     // is this notification currently being intercepted by Zen Mode?
    114     private boolean mIntercept;
    115 
    116     // is this notification hidden since the app pkg is suspended?
    117     private boolean mHidden;
    118 
    119     // The timestamp used for ranking.
    120     private long mRankingTimeMs;
    121 
    122     // The first post time, stable across updates.
    123     private long mCreationTimeMs;
    124 
    125     // The most recent visibility event.
    126     private long mVisibleSinceMs;
    127 
    128     // The most recent update time, or the creation time if no updates.
    129     private long mUpdateTimeMs;
    130 
    131     // Is this record an update of an old record?
    132     public boolean isUpdate;
    133     private int mPackagePriority;
    134 
    135     private int mAuthoritativeRank;
    136     private String mGlobalSortKey;
    137     private int mPackageVisibility;
    138     private int mUserImportance = IMPORTANCE_UNSPECIFIED;
    139     private int mImportance = IMPORTANCE_UNSPECIFIED;
    140     private CharSequence mImportanceExplanation = null;
    141 
    142     private int mSuppressedVisualEffects = 0;
    143     private String mUserExplanation;
    144     private String mPeopleExplanation;
    145     private boolean mPreChannelsNotification = true;
    146     private Uri mSound;
    147     private long[] mVibration;
    148     private AudioAttributes mAttributes;
    149     private NotificationChannel mChannel;
    150     private ArrayList<String> mPeopleOverride;
    151     private ArrayList<SnoozeCriterion> mSnoozeCriteria;
    152     private boolean mShowBadge;
    153     private LogMaker mLogMaker;
    154     private Light mLight;
    155     private String mGroupLogTag;
    156     private String mChannelIdLogTag;
    157 
    158     private final List<Adjustment> mAdjustments;
    159     private final NotificationStats mStats;
    160     private int mUserSentiment;
    161     private boolean mIsInterruptive;
    162     private boolean mTextChanged;
    163     private boolean mRecordedInterruption;
    164     private int mNumberOfSmartRepliesAdded;
    165     private boolean mHasSeenSmartReplies;
    166     /**
    167      * Whether this notification (and its channels) should be considered user locked. Used in
    168      * conjunction with user sentiment calculation.
    169      */
    170     private boolean mIsAppImportanceLocked;
    171     private ArraySet<Uri> mGrantableUris;
    172 
    173     public NotificationRecord(Context context, StatusBarNotification sbn,
    174             NotificationChannel channel) {
    175         this.sbn = sbn;
    176         mTargetSdkVersion = LocalServices.getService(PackageManagerInternal.class)
    177                 .getPackageTargetSdkVersion(sbn.getPackageName());
    178         mAm = ActivityManager.getService();
    179         mOriginalFlags = sbn.getNotification().flags;
    180         mRankingTimeMs = calculateRankingTimeMs(0L);
    181         mCreationTimeMs = sbn.getPostTime();
    182         mUpdateTimeMs = mCreationTimeMs;
    183         mContext = context;
    184         stats = new NotificationUsageStats.SingleNotificationStats();
    185         mChannel = channel;
    186         mPreChannelsNotification = isPreChannelsNotification();
    187         mSound = calculateSound();
    188         mVibration = calculateVibration();
    189         mAttributes = calculateAttributes();
    190         mImportance = calculateImportance();
    191         mLight = calculateLights();
    192         mAdjustments = new ArrayList<>();
    193         mStats = new NotificationStats();
    194         calculateUserSentiment();
    195         calculateGrantableUris();
    196     }
    197 
    198     private boolean isPreChannelsNotification() {
    199         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(getChannel().getId())) {
    200             if (mTargetSdkVersion < Build.VERSION_CODES.O) {
    201                 return true;
    202             }
    203         }
    204         return false;
    205     }
    206 
    207     private Uri calculateSound() {
    208         final Notification n = sbn.getNotification();
    209 
    210         // No notification sounds on tv
    211         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
    212             return null;
    213         }
    214 
    215         Uri sound = mChannel.getSound();
    216         if (mPreChannelsNotification && (getChannel().getUserLockedFields()
    217                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
    218 
    219             final boolean useDefaultSound = (n.defaults & Notification.DEFAULT_SOUND) != 0;
    220             if (useDefaultSound) {
    221                 sound = Settings.System.DEFAULT_NOTIFICATION_URI;
    222             } else {
    223                 sound = n.sound;
    224             }
    225         }
    226         return sound;
    227     }
    228 
    229     private Light calculateLights() {
    230         int defaultLightColor = mContext.getResources().getColor(
    231                 com.android.internal.R.color.config_defaultNotificationColor);
    232         int defaultLightOn = mContext.getResources().getInteger(
    233                 com.android.internal.R.integer.config_defaultNotificationLedOn);
    234         int defaultLightOff = mContext.getResources().getInteger(
    235                 com.android.internal.R.integer.config_defaultNotificationLedOff);
    236 
    237         int channelLightColor = getChannel().getLightColor() != 0 ? getChannel().getLightColor()
    238                 : defaultLightColor;
    239         Light light = getChannel().shouldShowLights() ? new Light(channelLightColor,
    240                 defaultLightOn, defaultLightOff) : null;
    241         if (mPreChannelsNotification
    242                 && (getChannel().getUserLockedFields()
    243                 & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
    244             final Notification notification = sbn.getNotification();
    245             if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
    246                 light = new Light(notification.ledARGB, notification.ledOnMS,
    247                         notification.ledOffMS);
    248                 if ((notification.defaults & Notification.DEFAULT_LIGHTS) != 0) {
    249                     light = new Light(defaultLightColor, defaultLightOn,
    250                             defaultLightOff);
    251                 }
    252             } else {
    253                 light = null;
    254             }
    255         }
    256         return light;
    257     }
    258 
    259     private long[] calculateVibration() {
    260         long[] vibration;
    261         final long[] defaultVibration =  NotificationManagerService.getLongArray(
    262                 mContext.getResources(),
    263                 com.android.internal.R.array.config_defaultNotificationVibePattern,
    264                 NotificationManagerService.VIBRATE_PATTERN_MAXLEN,
    265                 NotificationManagerService.DEFAULT_VIBRATE_PATTERN);
    266         if (getChannel().shouldVibrate()) {
    267             vibration = getChannel().getVibrationPattern() == null
    268                     ? defaultVibration : getChannel().getVibrationPattern();
    269         } else {
    270             vibration = null;
    271         }
    272         if (mPreChannelsNotification
    273                 && (getChannel().getUserLockedFields()
    274                 & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
    275             final Notification notification = sbn.getNotification();
    276             final boolean useDefaultVibrate =
    277                     (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
    278             if (useDefaultVibrate) {
    279                 vibration = defaultVibration;
    280             } else {
    281                 vibration = notification.vibrate;
    282             }
    283         }
    284         return vibration;
    285     }
    286 
    287     private AudioAttributes calculateAttributes() {
    288         final Notification n = sbn.getNotification();
    289         AudioAttributes attributes = getChannel().getAudioAttributes();
    290         if (attributes == null) {
    291             attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
    292         }
    293 
    294         if (mPreChannelsNotification
    295                 && (getChannel().getUserLockedFields()
    296                 & NotificationChannel.USER_LOCKED_SOUND) == 0) {
    297             if (n.audioAttributes != null) {
    298                 // prefer audio attributes to stream type
    299                 attributes = n.audioAttributes;
    300             } else if (n.audioStreamType >= 0
    301                     && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
    302                 // the stream type is valid, use it
    303                 attributes = new AudioAttributes.Builder()
    304                         .setInternalLegacyStreamType(n.audioStreamType)
    305                         .build();
    306             } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
    307                 Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
    308             }
    309         }
    310         return attributes;
    311     }
    312 
    313     private int calculateImportance() {
    314         final Notification n = sbn.getNotification();
    315         int importance = getChannel().getImportance();
    316         int requestedImportance = IMPORTANCE_DEFAULT;
    317 
    318         // Migrate notification flags to scores
    319         if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
    320             n.priority = Notification.PRIORITY_MAX;
    321         }
    322 
    323         n.priority = NotificationManagerService.clamp(n.priority, Notification.PRIORITY_MIN,
    324                 Notification.PRIORITY_MAX);
    325         switch (n.priority) {
    326             case Notification.PRIORITY_MIN:
    327                 requestedImportance = IMPORTANCE_MIN;
    328                 break;
    329             case Notification.PRIORITY_LOW:
    330                 requestedImportance = IMPORTANCE_LOW;
    331                 break;
    332             case Notification.PRIORITY_DEFAULT:
    333                 requestedImportance = IMPORTANCE_DEFAULT;
    334                 break;
    335             case Notification.PRIORITY_HIGH:
    336             case Notification.PRIORITY_MAX:
    337                 requestedImportance = IMPORTANCE_HIGH;
    338                 break;
    339         }
    340         stats.requestedImportance = requestedImportance;
    341         stats.isNoisy = mSound != null || mVibration != null;
    342 
    343         if (mPreChannelsNotification
    344                 && (importance == IMPORTANCE_UNSPECIFIED
    345                 || (getChannel().getUserLockedFields()
    346                 & USER_LOCKED_IMPORTANCE) == 0)) {
    347             if (!stats.isNoisy && requestedImportance > IMPORTANCE_LOW) {
    348                 requestedImportance = IMPORTANCE_LOW;
    349             }
    350 
    351             if (stats.isNoisy) {
    352                 if (requestedImportance < IMPORTANCE_DEFAULT) {
    353                     requestedImportance = IMPORTANCE_DEFAULT;
    354                 }
    355             }
    356 
    357             if (n.fullScreenIntent != null) {
    358                 requestedImportance = IMPORTANCE_HIGH;
    359             }
    360             importance = requestedImportance;
    361         }
    362 
    363         stats.naturalImportance = importance;
    364         return importance;
    365     }
    366 
    367     // copy any notes that the ranking system may have made before the update
    368     public void copyRankingInformation(NotificationRecord previous) {
    369         mContactAffinity = previous.mContactAffinity;
    370         mRecentlyIntrusive = previous.mRecentlyIntrusive;
    371         mPackagePriority = previous.mPackagePriority;
    372         mPackageVisibility = previous.mPackageVisibility;
    373         mIntercept = previous.mIntercept;
    374         mHidden = previous.mHidden;
    375         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
    376         mCreationTimeMs = previous.mCreationTimeMs;
    377         mVisibleSinceMs = previous.mVisibleSinceMs;
    378         if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
    379             sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
    380         }
    381         // Don't copy importance information or mGlobalSortKey, recompute them.
    382     }
    383 
    384     public Notification getNotification() { return sbn.getNotification(); }
    385     public int getFlags() { return sbn.getNotification().flags; }
    386     public UserHandle getUser() { return sbn.getUser(); }
    387     public String getKey() { return sbn.getKey(); }
    388     /** @deprecated Use {@link #getUser()} instead. */
    389     public int getUserId() { return sbn.getUserId(); }
    390     public int getUid() { return sbn.getUid(); }
    391 
    392     void dump(ProtoOutputStream proto, long fieldId, boolean redact, int state) {
    393         final long token = proto.start(fieldId);
    394 
    395         proto.write(NotificationRecordProto.KEY, sbn.getKey());
    396         proto.write(NotificationRecordProto.STATE, state);
    397         if (getChannel() != null) {
    398             proto.write(NotificationRecordProto.CHANNEL_ID, getChannel().getId());
    399         }
    400         proto.write(NotificationRecordProto.CAN_SHOW_LIGHT, getLight() != null);
    401         proto.write(NotificationRecordProto.CAN_VIBRATE, getVibration() != null);
    402         proto.write(NotificationRecordProto.FLAGS, sbn.getNotification().flags);
    403         proto.write(NotificationRecordProto.GROUP_KEY, getGroupKey());
    404         proto.write(NotificationRecordProto.IMPORTANCE, getImportance());
    405         if (getSound() != null) {
    406             proto.write(NotificationRecordProto.SOUND, getSound().toString());
    407         }
    408         if (getAudioAttributes() != null) {
    409             getAudioAttributes().writeToProto(proto, NotificationRecordProto.AUDIO_ATTRIBUTES);
    410         }
    411 
    412         proto.end(token);
    413     }
    414 
    415     String formatRemoteViews(RemoteViews rv) {
    416         if (rv == null) return "null";
    417         return String.format("%s/0x%08x (%d bytes): %s",
    418             rv.getPackage(), rv.getLayoutId(), rv.estimateMemoryUsage(), rv.toString());
    419     }
    420 
    421     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
    422         final Notification notification = sbn.getNotification();
    423         final Icon icon = notification.getSmallIcon();
    424         String iconStr = String.valueOf(icon);
    425         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
    426             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
    427         }
    428         pw.println(prefix + this);
    429         prefix = prefix + "  ";
    430         pw.println(prefix + "uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
    431         pw.println(prefix + "icon=" + iconStr);
    432         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
    433         pw.println(prefix + "pri=" + notification.priority);
    434         pw.println(prefix + "key=" + sbn.getKey());
    435         pw.println(prefix + "seen=" + mStats.hasSeen());
    436         pw.println(prefix + "groupKey=" + getGroupKey());
    437         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
    438         pw.println(prefix + "contentIntent=" + notification.contentIntent);
    439         pw.println(prefix + "deleteIntent=" + notification.deleteIntent);
    440 
    441         pw.print(prefix + "tickerText=");
    442         if (!TextUtils.isEmpty(notification.tickerText)) {
    443             final String ticker = notification.tickerText.toString();
    444             if (redact) {
    445                 // if the string is long enough, we allow ourselves a few bytes for debugging
    446                 pw.print(ticker.length() > 16 ? ticker.substring(0,8) : "");
    447                 pw.println("...");
    448             } else {
    449                 pw.println(ticker);
    450             }
    451         } else {
    452             pw.println("null");
    453         }
    454         pw.println(prefix + "contentView=" + formatRemoteViews(notification.contentView));
    455         pw.println(prefix + "bigContentView=" + formatRemoteViews(notification.bigContentView));
    456         pw.println(prefix + "headsUpContentView="
    457                 + formatRemoteViews(notification.headsUpContentView));
    458         pw.print(prefix + String.format("color=0x%08x", notification.color));
    459         pw.println(prefix + "timeout="
    460                 + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
    461         if (notification.actions != null && notification.actions.length > 0) {
    462             pw.println(prefix + "actions={");
    463             final int N = notification.actions.length;
    464             for (int i = 0; i < N; i++) {
    465                 final Notification.Action action = notification.actions[i];
    466                 if (action != null) {
    467                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
    468                             prefix,
    469                             i,
    470                             action.title,
    471                             action.actionIntent == null ? "null" : action.actionIntent.toString()
    472                     ));
    473                 }
    474             }
    475             pw.println(prefix + "  }");
    476         }
    477         if (notification.extras != null && notification.extras.size() > 0) {
    478             pw.println(prefix + "extras={");
    479             for (String key : notification.extras.keySet()) {
    480                 pw.print(prefix + "    " + key + "=");
    481                 Object val = notification.extras.get(key);
    482                 if (val == null) {
    483                     pw.println("null");
    484                 } else {
    485                     pw.print(val.getClass().getSimpleName());
    486                     if (redact && (val instanceof CharSequence || val instanceof String)) {
    487                         // redact contents from bugreports
    488                     } else if (val instanceof Bitmap) {
    489                         pw.print(String.format(" (%dx%d)",
    490                                 ((Bitmap) val).getWidth(),
    491                                 ((Bitmap) val).getHeight()));
    492                     } else if (val.getClass().isArray()) {
    493                         final int N = Array.getLength(val);
    494                         pw.print(" (" + N + ")");
    495                         if (!redact) {
    496                             for (int j = 0; j < N; j++) {
    497                                 pw.println();
    498                                 pw.print(String.format("%s      [%d] %s",
    499                                         prefix, j, String.valueOf(Array.get(val, j))));
    500                             }
    501                         }
    502                     } else {
    503                         pw.print(" (" + String.valueOf(val) + ")");
    504                     }
    505                     pw.println();
    506                 }
    507             }
    508             pw.println(prefix + "}");
    509         }
    510         pw.println(prefix + "stats=" + stats.toString());
    511         pw.println(prefix + "mContactAffinity=" + mContactAffinity);
    512         pw.println(prefix + "mRecentlyIntrusive=" + mRecentlyIntrusive);
    513         pw.println(prefix + "mPackagePriority=" + mPackagePriority);
    514         pw.println(prefix + "mPackageVisibility=" + mPackageVisibility);
    515         pw.println(prefix + "mUserImportance="
    516                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
    517         pw.println(prefix + "mImportance="
    518                 + NotificationListenerService.Ranking.importanceToString(mImportance));
    519         pw.println(prefix + "mImportanceExplanation=" + mImportanceExplanation);
    520         pw.println(prefix + "mIsAppImportanceLocked=" + mIsAppImportanceLocked);
    521         pw.println(prefix + "mIntercept=" + mIntercept);
    522         pw.println(prefix + "mHidden==" + mHidden);
    523         pw.println(prefix + "mGlobalSortKey=" + mGlobalSortKey);
    524         pw.println(prefix + "mRankingTimeMs=" + mRankingTimeMs);
    525         pw.println(prefix + "mCreationTimeMs=" + mCreationTimeMs);
    526         pw.println(prefix + "mVisibleSinceMs=" + mVisibleSinceMs);
    527         pw.println(prefix + "mUpdateTimeMs=" + mUpdateTimeMs);
    528         pw.println(prefix + "mSuppressedVisualEffects= " + mSuppressedVisualEffects);
    529         if (mPreChannelsNotification) {
    530             pw.println(prefix + String.format("defaults=0x%08x flags=0x%08x",
    531                     notification.defaults, notification.flags));
    532             pw.println(prefix + "n.sound=" + notification.sound);
    533             pw.println(prefix + "n.audioStreamType=" + notification.audioStreamType);
    534             pw.println(prefix + "n.audioAttributes=" + notification.audioAttributes);
    535             pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
    536                     notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
    537             pw.println(prefix + "vibrate=" + Arrays.toString(notification.vibrate));
    538         }
    539         pw.println(prefix + "mSound= " + mSound);
    540         pw.println(prefix + "mVibration= " + mVibration);
    541         pw.println(prefix + "mAttributes= " + mAttributes);
    542         pw.println(prefix + "mLight= " + mLight);
    543         pw.println(prefix + "mShowBadge=" + mShowBadge);
    544         pw.println(prefix + "mColorized=" + notification.isColorized());
    545         pw.println(prefix + "mIsInterruptive=" + mIsInterruptive);
    546         pw.println(prefix + "effectiveNotificationChannel=" + getChannel());
    547         if (getPeopleOverride() != null) {
    548             pw.println(prefix + "overridePeople= " + TextUtils.join(",", getPeopleOverride()));
    549         }
    550         if (getSnoozeCriteria() != null) {
    551             pw.println(prefix + "snoozeCriteria=" + TextUtils.join(",", getSnoozeCriteria()));
    552         }
    553         pw.println(prefix + "mAdjustments=" + mAdjustments);
    554     }
    555 
    556 
    557     static String idDebugString(Context baseContext, String packageName, int id) {
    558         Context c;
    559 
    560         if (packageName != null) {
    561             try {
    562                 c = baseContext.createPackageContext(packageName, 0);
    563             } catch (NameNotFoundException e) {
    564                 c = baseContext;
    565             }
    566         } else {
    567             c = baseContext;
    568         }
    569 
    570         Resources r = c.getResources();
    571         try {
    572             return r.getResourceName(id);
    573         } catch (Resources.NotFoundException e) {
    574             return "<name unknown>";
    575         }
    576     }
    577 
    578     @Override
    579     public final String toString() {
    580         return String.format(
    581                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s" +
    582                         "appImportanceLocked=%s: %s)",
    583                 System.identityHashCode(this),
    584                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
    585                 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
    586                 mIsAppImportanceLocked, this.sbn.getNotification());
    587     }
    588 
    589     public void addAdjustment(Adjustment adjustment) {
    590         synchronized (mAdjustments) {
    591             mAdjustments.add(adjustment);
    592         }
    593     }
    594 
    595     public void applyAdjustments() {
    596         synchronized (mAdjustments) {
    597             for (Adjustment adjustment: mAdjustments) {
    598                 Bundle signals = adjustment.getSignals();
    599                 if (signals.containsKey(Adjustment.KEY_PEOPLE)) {
    600                     final ArrayList<String> people =
    601                             adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
    602                     setPeopleOverride(people);
    603                 }
    604                 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
    605                     final ArrayList<SnoozeCriterion> snoozeCriterionList =
    606                             adjustment.getSignals().getParcelableArrayList(
    607                                     Adjustment.KEY_SNOOZE_CRITERIA);
    608                     setSnoozeCriteria(snoozeCriterionList);
    609                 }
    610                 if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
    611                     final String groupOverrideKey =
    612                             adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
    613                     setOverrideGroupKey(groupOverrideKey);
    614                 }
    615                 if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
    616                     // Only allow user sentiment update from assistant if user hasn't already
    617                     // expressed a preference for this channel
    618                     if (!mIsAppImportanceLocked
    619                             && (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
    620                         setUserSentiment(adjustment.getSignals().getInt(
    621                                 Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
    622                     }
    623                 }
    624             }
    625         }
    626     }
    627 
    628     public void setIsAppImportanceLocked(boolean isAppImportanceLocked) {
    629         mIsAppImportanceLocked = isAppImportanceLocked;
    630         calculateUserSentiment();
    631     }
    632 
    633     public void setContactAffinity(float contactAffinity) {
    634         mContactAffinity = contactAffinity;
    635         if (mImportance < IMPORTANCE_DEFAULT &&
    636                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
    637             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
    638         }
    639     }
    640 
    641     public float getContactAffinity() {
    642         return mContactAffinity;
    643     }
    644 
    645     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
    646         mRecentlyIntrusive = recentlyIntrusive;
    647         if (recentlyIntrusive) {
    648             mLastIntrusive = System.currentTimeMillis();
    649         }
    650     }
    651 
    652     public boolean isRecentlyIntrusive() {
    653         return mRecentlyIntrusive;
    654     }
    655 
    656     public long getLastIntrusive() {
    657         return mLastIntrusive;
    658     }
    659 
    660     public void setPackagePriority(int packagePriority) {
    661         mPackagePriority = packagePriority;
    662     }
    663 
    664     public int getPackagePriority() {
    665         return mPackagePriority;
    666     }
    667 
    668     public void setPackageVisibilityOverride(int packageVisibility) {
    669         mPackageVisibility = packageVisibility;
    670     }
    671 
    672     public int getPackageVisibilityOverride() {
    673         return mPackageVisibility;
    674     }
    675 
    676     public void setUserImportance(int importance) {
    677         mUserImportance = importance;
    678         applyUserImportance();
    679     }
    680 
    681     private String getUserExplanation() {
    682         if (mUserExplanation == null) {
    683             mUserExplanation = mContext.getResources().getString(
    684                     com.android.internal.R.string.importance_from_user);
    685         }
    686         return mUserExplanation;
    687     }
    688 
    689     private String getPeopleExplanation() {
    690         if (mPeopleExplanation == null) {
    691             mPeopleExplanation = mContext.getResources().getString(
    692                     com.android.internal.R.string.importance_from_person);
    693         }
    694         return mPeopleExplanation;
    695     }
    696 
    697     private void applyUserImportance() {
    698         if (mUserImportance != IMPORTANCE_UNSPECIFIED) {
    699             mImportance = mUserImportance;
    700             mImportanceExplanation = getUserExplanation();
    701         }
    702     }
    703 
    704     public int getUserImportance() {
    705         return mUserImportance;
    706     }
    707 
    708     public void setImportance(int importance, CharSequence explanation) {
    709         if (importance != IMPORTANCE_UNSPECIFIED) {
    710             mImportance = importance;
    711             mImportanceExplanation = explanation;
    712         }
    713         applyUserImportance();
    714     }
    715 
    716     public int getImportance() {
    717         return mImportance;
    718     }
    719 
    720     public CharSequence getImportanceExplanation() {
    721         return mImportanceExplanation;
    722     }
    723 
    724     public boolean setIntercepted(boolean intercept) {
    725         mIntercept = intercept;
    726         return mIntercept;
    727     }
    728 
    729     public boolean isIntercepted() {
    730         return mIntercept;
    731     }
    732 
    733     public void setHidden(boolean hidden) {
    734         mHidden = hidden;
    735     }
    736 
    737     public boolean isHidden() {
    738         return mHidden;
    739     }
    740 
    741 
    742     public void setSuppressedVisualEffects(int effects) {
    743         mSuppressedVisualEffects = effects;
    744     }
    745 
    746     public int getSuppressedVisualEffects() {
    747         return mSuppressedVisualEffects;
    748     }
    749 
    750     public boolean isCategory(String category) {
    751         return Objects.equals(getNotification().category, category);
    752     }
    753 
    754     public boolean isAudioAttributesUsage(int usage) {
    755         return mAttributes != null && mAttributes.getUsage() == usage;
    756     }
    757 
    758     /**
    759      * Returns the timestamp to use for time-based sorting in the ranker.
    760      */
    761     public long getRankingTimeMs() {
    762         return mRankingTimeMs;
    763     }
    764 
    765     /**
    766      * @param now this current time in milliseconds.
    767      * @returns the number of milliseconds since the most recent update, or the post time if none.
    768      */
    769     public int getFreshnessMs(long now) {
    770         return (int) (now - mUpdateTimeMs);
    771     }
    772 
    773     /**
    774      * @param now this current time in milliseconds.
    775      * @returns the number of milliseconds since the the first post, ignoring updates.
    776      */
    777     public int getLifespanMs(long now) {
    778         return (int) (now - mCreationTimeMs);
    779     }
    780 
    781     /**
    782      * @param now this current time in milliseconds.
    783      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
    784      */
    785     public int getExposureMs(long now) {
    786         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
    787     }
    788 
    789     /**
    790      * Set the visibility of the notification.
    791      */
    792     public void setVisibility(boolean visible, int rank, int count) {
    793         final long now = System.currentTimeMillis();
    794         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
    795         stats.onVisibilityChanged(visible);
    796         MetricsLogger.action(getLogMaker(now)
    797                 .setCategory(MetricsEvent.NOTIFICATION_ITEM)
    798                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
    799                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank)
    800                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_COUNT, count));
    801         if (visible) {
    802             setSeen();
    803             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
    804         }
    805         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
    806                 getLifespanMs(now),
    807                 getFreshnessMs(now),
    808                 0, // exposure time
    809                 rank);
    810     }
    811 
    812     /**
    813      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
    814      *     of the previous notification record, 0 otherwise
    815      */
    816     private long calculateRankingTimeMs(long previousRankingTimeMs) {
    817         Notification n = getNotification();
    818         // Take developer provided 'when', unless it's in the future.
    819         if (n.when != 0 && n.when <= sbn.getPostTime()) {
    820             return n.when;
    821         }
    822         // If we've ranked a previous instance with a timestamp, inherit it. This case is
    823         // important in order to have ranking stability for updating notifications.
    824         if (previousRankingTimeMs > 0) {
    825             return previousRankingTimeMs;
    826         }
    827         return sbn.getPostTime();
    828     }
    829 
    830     public void setGlobalSortKey(String globalSortKey) {
    831         mGlobalSortKey = globalSortKey;
    832     }
    833 
    834     public String getGlobalSortKey() {
    835         return mGlobalSortKey;
    836     }
    837 
    838     /** Check if any of the listeners have marked this notification as seen by the user. */
    839     public boolean isSeen() {
    840         return mStats.hasSeen();
    841     }
    842 
    843     /** Mark the notification as seen by the user. */
    844     public void setSeen() {
    845         mStats.setSeen();
    846         if (mTextChanged) {
    847             mIsInterruptive = true;
    848         }
    849     }
    850 
    851     public void setAuthoritativeRank(int authoritativeRank) {
    852         mAuthoritativeRank = authoritativeRank;
    853     }
    854 
    855     public int getAuthoritativeRank() {
    856         return mAuthoritativeRank;
    857     }
    858 
    859     public String getGroupKey() {
    860         return sbn.getGroupKey();
    861     }
    862 
    863     public void setOverrideGroupKey(String overrideGroupKey) {
    864         sbn.setOverrideGroupKey(overrideGroupKey);
    865         mGroupLogTag = null;
    866     }
    867 
    868     private String getGroupLogTag() {
    869         if (mGroupLogTag == null) {
    870             mGroupLogTag = shortenTag(sbn.getGroup());
    871         }
    872         return mGroupLogTag;
    873     }
    874 
    875     private String getChannelIdLogTag() {
    876         if (mChannelIdLogTag == null) {
    877             mChannelIdLogTag = shortenTag(mChannel.getId());
    878         }
    879         return mChannelIdLogTag;
    880     }
    881 
    882     private String shortenTag(String longTag) {
    883         if (longTag == null) {
    884             return null;
    885         }
    886         if (longTag.length() < MAX_LOGTAG_LENGTH) {
    887             return longTag;
    888         } else {
    889             return longTag.substring(0, MAX_LOGTAG_LENGTH - 8) + "-" +
    890                     Integer.toHexString(longTag.hashCode());
    891         }
    892     }
    893 
    894     public NotificationChannel getChannel() {
    895         return mChannel;
    896     }
    897 
    898     /**
    899      * @see RankingHelper#getIsAppImportanceLocked(String, int)
    900      */
    901     public boolean getIsAppImportanceLocked() {
    902         return mIsAppImportanceLocked;
    903     }
    904 
    905     protected void updateNotificationChannel(NotificationChannel channel) {
    906         if (channel != null) {
    907             mChannel = channel;
    908             calculateImportance();
    909             calculateUserSentiment();
    910         }
    911     }
    912 
    913     public void setShowBadge(boolean showBadge) {
    914         mShowBadge = showBadge;
    915     }
    916 
    917     public boolean canShowBadge() {
    918         return mShowBadge;
    919     }
    920 
    921     public Light getLight() {
    922         return mLight;
    923     }
    924 
    925     public Uri getSound() {
    926         return mSound;
    927     }
    928 
    929     public long[] getVibration() {
    930         return mVibration;
    931     }
    932 
    933     public AudioAttributes getAudioAttributes() {
    934         return mAttributes;
    935     }
    936 
    937     public ArrayList<String> getPeopleOverride() {
    938         return mPeopleOverride;
    939     }
    940 
    941     public void setInterruptive(boolean interruptive) {
    942         mIsInterruptive = interruptive;
    943     }
    944 
    945     public void setTextChanged(boolean textChanged) {
    946         mTextChanged = textChanged;
    947     }
    948 
    949     public void setRecordedInterruption(boolean recorded) {
    950         mRecordedInterruption = recorded;
    951     }
    952 
    953     public boolean hasRecordedInterruption() {
    954         return mRecordedInterruption;
    955     }
    956 
    957     public boolean isInterruptive() {
    958         return mIsInterruptive;
    959     }
    960 
    961     protected void setPeopleOverride(ArrayList<String> people) {
    962         mPeopleOverride = people;
    963     }
    964 
    965     public ArrayList<SnoozeCriterion> getSnoozeCriteria() {
    966         return mSnoozeCriteria;
    967     }
    968 
    969     protected void setSnoozeCriteria(ArrayList<SnoozeCriterion> snoozeCriteria) {
    970         mSnoozeCriteria = snoozeCriteria;
    971     }
    972 
    973     private void calculateUserSentiment() {
    974         if ((getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0
    975                 || mIsAppImportanceLocked) {
    976             mUserSentiment = USER_SENTIMENT_POSITIVE;
    977         }
    978     }
    979 
    980     private void setUserSentiment(int userSentiment) {
    981         mUserSentiment = userSentiment;
    982     }
    983 
    984     public int getUserSentiment() {
    985         return mUserSentiment;
    986     }
    987 
    988     public NotificationStats getStats() {
    989         return mStats;
    990     }
    991 
    992     public void recordExpanded() {
    993         mStats.setExpanded();
    994     }
    995 
    996     public void recordDirectReplied() {
    997         mStats.setDirectReplied();
    998     }
    999 
   1000     public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
   1001         mStats.setDismissalSurface(surface);
   1002     }
   1003 
   1004     public void recordSnoozed() {
   1005         mStats.setSnoozed();
   1006     }
   1007 
   1008     public void recordViewedSettings() {
   1009         mStats.setViewedSettings();
   1010     }
   1011 
   1012     public void setNumSmartRepliesAdded(int noReplies) {
   1013         mNumberOfSmartRepliesAdded = noReplies;
   1014     }
   1015 
   1016     public int getNumSmartRepliesAdded() {
   1017         return mNumberOfSmartRepliesAdded;
   1018     }
   1019 
   1020     public boolean hasSeenSmartReplies() {
   1021         return mHasSeenSmartReplies;
   1022     }
   1023 
   1024     public void setSeenSmartReplies(boolean hasSeenSmartReplies) {
   1025         mHasSeenSmartReplies = hasSeenSmartReplies;
   1026     }
   1027 
   1028     /**
   1029      * @return all {@link Uri} that should have permission granted to whoever
   1030      *         will be rendering it. This list has already been vetted to only
   1031      *         include {@link Uri} that the enqueuing app can grant.
   1032      */
   1033     public @Nullable ArraySet<Uri> getGrantableUris() {
   1034         return mGrantableUris;
   1035     }
   1036 
   1037     /**
   1038      * Collect all {@link Uri} that should have permission granted to whoever
   1039      * will be rendering it.
   1040      */
   1041     protected void calculateGrantableUris() {
   1042         final Notification notification = getNotification();
   1043         notification.visitUris((uri) -> {
   1044             visitGrantableUri(uri, false);
   1045         });
   1046 
   1047         if (notification.getChannelId() != null) {
   1048             NotificationChannel channel = getChannel();
   1049             if (channel != null) {
   1050                 visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
   1051                         & NotificationChannel.USER_LOCKED_SOUND) != 0);
   1052             }
   1053         }
   1054     }
   1055 
   1056     /**
   1057      * Note the presence of a {@link Uri} that should have permission granted to
   1058      * whoever will be rendering it.
   1059      * <p>
   1060      * If the enqueuing app has the ability to grant access, it will be added to
   1061      * {@link #mGrantableUris}. Otherwise, this will either log or throw
   1062      * {@link SecurityException} depending on target SDK of enqueuing app.
   1063      */
   1064     private void visitGrantableUri(Uri uri, boolean userOverriddenUri) {
   1065         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
   1066 
   1067         // We can't grant Uri permissions from system
   1068         final int sourceUid = sbn.getUid();
   1069         if (sourceUid == android.os.Process.SYSTEM_UID) return;
   1070 
   1071         final long ident = Binder.clearCallingIdentity();
   1072         try {
   1073             // This will throw SecurityException if caller can't grant
   1074             mAm.checkGrantUriPermission(sourceUid, null,
   1075                     ContentProvider.getUriWithoutUserId(uri),
   1076                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
   1077                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
   1078 
   1079             if (mGrantableUris == null) {
   1080                 mGrantableUris = new ArraySet<>();
   1081             }
   1082             mGrantableUris.add(uri);
   1083         } catch (RemoteException ignored) {
   1084             // Ignored because we're in same process
   1085         } catch (SecurityException e) {
   1086             if (!userOverriddenUri) {
   1087                 if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
   1088                     throw e;
   1089                 } else {
   1090                     Log.w(TAG, "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
   1091                 }
   1092             }
   1093         } finally {
   1094             Binder.restoreCallingIdentity(ident);
   1095         }
   1096     }
   1097 
   1098     public LogMaker getLogMaker(long now) {
   1099         if (mLogMaker == null) {
   1100             // initialize fields that only change on update (so a new record)
   1101             mLogMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN)
   1102                     .setPackageName(sbn.getPackageName())
   1103                     .addTaggedData(MetricsEvent.NOTIFICATION_ID, sbn.getId())
   1104                     .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag())
   1105                     .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag());
   1106         }
   1107         // reset fields that can change between updates, or are used by multiple logs
   1108         return mLogMaker
   1109                 .clearCategory()
   1110                 .clearType()
   1111                 .clearSubtype()
   1112                 .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX)
   1113                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
   1114                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
   1115                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
   1116                         sbn.getNotification().isGroupSummary() ? 1 : 0)
   1117                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now))
   1118                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now))
   1119                 .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, getExposureMs(now));
   1120     }
   1121 
   1122     public LogMaker getLogMaker() {
   1123         return getLogMaker(System.currentTimeMillis());
   1124     }
   1125 
   1126     @VisibleForTesting
   1127     static final class Light {
   1128         public final int color;
   1129         public final int onMs;
   1130         public final int offMs;
   1131 
   1132         public Light(int color, int onMs, int offMs) {
   1133             this.color = color;
   1134             this.onMs = onMs;
   1135             this.offMs = offMs;
   1136         }
   1137 
   1138         @Override
   1139         public boolean equals(Object o) {
   1140             if (this == o) return true;
   1141             if (o == null || getClass() != o.getClass()) return false;
   1142 
   1143             Light light = (Light) o;
   1144 
   1145             if (color != light.color) return false;
   1146             if (onMs != light.onMs) return false;
   1147             return offMs == light.offMs;
   1148 
   1149         }
   1150 
   1151         @Override
   1152         public int hashCode() {
   1153             int result = color;
   1154             result = 31 * result + onMs;
   1155             result = 31 * result + offMs;
   1156             return result;
   1157         }
   1158 
   1159         @Override
   1160         public String toString() {
   1161             return "Light{" +
   1162                     "color=" + color +
   1163                     ", onMs=" + onMs +
   1164                     ", offMs=" + offMs +
   1165                     '}';
   1166         }
   1167     }
   1168 }
   1169