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 */
16package com.android.server.notification;
17
18import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
19import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20import static android.app.NotificationManager.IMPORTANCE_HIGH;
21import static android.app.NotificationManager.IMPORTANCE_LOW;
22import static android.app.NotificationManager.IMPORTANCE_MIN;
23import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
24import static android.service.notification.NotificationListenerService.Ranking
25        .USER_SENTIMENT_NEUTRAL;
26import static android.service.notification.NotificationListenerService.Ranking
27        .USER_SENTIMENT_POSITIVE;
28
29import android.annotation.Nullable;
30import android.app.ActivityManager;
31import android.app.IActivityManager;
32import android.app.Notification;
33import android.app.NotificationChannel;
34import android.content.ContentProvider;
35import android.content.ContentResolver;
36import android.content.Context;
37import android.content.Intent;
38import android.content.pm.PackageManager;
39import android.content.pm.PackageManager.NameNotFoundException;
40import android.content.pm.PackageManagerInternal;
41import android.content.res.Resources;
42import android.graphics.Bitmap;
43import android.graphics.drawable.Icon;
44import android.media.AudioAttributes;
45import android.media.AudioSystem;
46import android.metrics.LogMaker;
47import android.net.Uri;
48import android.os.Binder;
49import android.os.Build;
50import android.os.Bundle;
51import android.os.IBinder;
52import android.os.RemoteException;
53import android.os.UserHandle;
54import android.provider.Settings;
55import android.service.notification.Adjustment;
56import android.service.notification.NotificationListenerService;
57import android.service.notification.NotificationRecordProto;
58import android.service.notification.NotificationStats;
59import android.service.notification.SnoozeCriterion;
60import android.service.notification.StatusBarNotification;
61import android.text.TextUtils;
62import android.util.ArraySet;
63import android.util.Log;
64import android.util.TimeUtils;
65import android.util.proto.ProtoOutputStream;
66import android.widget.RemoteViews;
67
68import com.android.internal.annotations.VisibleForTesting;
69import com.android.internal.logging.MetricsLogger;
70import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
71import com.android.server.EventLogTags;
72import com.android.server.LocalServices;
73
74import java.io.PrintWriter;
75import java.lang.reflect.Array;
76import java.util.ArrayList;
77import java.util.Arrays;
78import java.util.List;
79import 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 */
93public 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