NotificationRecord.java revision 1f602dc3264c2aaf3d9f1552077de1c8a0bbf56e
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.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MIN;
19import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
20import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
21import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
22import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW;
23import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
24
25import android.app.Notification;
26import android.content.Context;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.res.Resources;
30import android.graphics.Bitmap;
31import android.graphics.drawable.Icon;
32import android.media.AudioAttributes;
33import android.os.Build;
34import android.os.UserHandle;
35import android.service.notification.NotificationListenerService;
36import android.service.notification.StatusBarNotification;
37import android.util.Log;
38import android.util.Slog;
39
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.server.EventLogTags;
42
43import java.io.PrintWriter;
44import java.lang.reflect.Array;
45import java.util.Arrays;
46import java.util.Objects;
47
48/**
49 * Holds data about notifications that should not be shared with the
50 * {@link android.service.notification.NotificationListenerService}s.
51 *
52 * <p>These objects should not be mutated unless the code is synchronized
53 * on {@link NotificationManagerService#mNotificationList}, and any
54 * modification should be followed by a sorting of that list.</p>
55 *
56 * <p>Is sortable by {@link NotificationComparator}.</p>
57 *
58 * {@hide}
59 */
60public final class NotificationRecord {
61    static final String TAG = "NotificationRecord";
62    static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
63    final StatusBarNotification sbn;
64    final int mOriginalFlags;
65    private final Context mContext;
66
67    NotificationUsageStats.SingleNotificationStats stats;
68    boolean isCanceled;
69    /** Whether the notification was seen by the user via one of the notification listeners. */
70    boolean mIsSeen;
71
72    // These members are used by NotificationSignalExtractors
73    // to communicate with the ranking module.
74    private float mContactAffinity;
75    private boolean mRecentlyIntrusive;
76
77    // is this notification currently being intercepted by Zen Mode?
78    private boolean mIntercept;
79
80    // The timestamp used for ranking.
81    private long mRankingTimeMs;
82
83    // The first post time, stable across updates.
84    private long mCreationTimeMs;
85
86    // The most recent visibility event.
87    private long mVisibleSinceMs;
88
89    // The most recent update time, or the creation time if no updates.
90    private long mUpdateTimeMs;
91
92    // Is this record an update of an old record?
93    public boolean isUpdate;
94    private int mPackagePriority;
95
96    private int mAuthoritativeRank;
97    private String mGlobalSortKey;
98    private int mPackageVisibility;
99    private int mUserImportance = IMPORTANCE_UNSPECIFIED;
100    private int mImportance = IMPORTANCE_UNSPECIFIED;
101    private CharSequence mImportanceExplanation = null;
102
103    private int mSuppressedVisualEffects = 0;
104    private String mUserExplanation;
105    private String mPeopleExplanation;
106
107    @VisibleForTesting
108    public NotificationRecord(Context context, StatusBarNotification sbn)
109    {
110        this.sbn = sbn;
111        mOriginalFlags = sbn.getNotification().flags;
112        mRankingTimeMs = calculateRankingTimeMs(0L);
113        mCreationTimeMs = sbn.getPostTime();
114        mUpdateTimeMs = mCreationTimeMs;
115        mContext = context;
116        stats = new NotificationUsageStats.SingleNotificationStats();
117        mImportance = defaultImportance();
118    }
119
120    private int defaultImportance() {
121        final Notification n = sbn.getNotification();
122        int importance = IMPORTANCE_DEFAULT;
123
124        // Migrate notification flags to scores
125        if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
126            n.priority = Notification.PRIORITY_MAX;
127        }
128
129        switch (n.priority) {
130            case Notification.PRIORITY_MIN:
131                importance = IMPORTANCE_MIN;
132                break;
133            case Notification.PRIORITY_LOW:
134                importance = IMPORTANCE_LOW;
135                break;
136            case Notification.PRIORITY_DEFAULT:
137                importance = IMPORTANCE_DEFAULT;
138                break;
139            case Notification.PRIORITY_HIGH:
140                importance = IMPORTANCE_HIGH;
141                break;
142            case Notification.PRIORITY_MAX:
143                importance = IMPORTANCE_MAX;
144                break;
145        }
146        stats.requestedImportance = importance;
147
148        boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0
149                || (n.defaults & Notification.DEFAULT_VIBRATE) != 0
150                || n.sound != null
151                || n.vibrate != null;
152        stats.isNoisy = isNoisy;
153
154        if (!isNoisy && importance > IMPORTANCE_LOW) {
155            importance = IMPORTANCE_LOW;
156        }
157
158        if (isNoisy) {
159            if (importance < IMPORTANCE_DEFAULT) {
160                importance = IMPORTANCE_DEFAULT;
161            }
162        }
163
164        if (n.fullScreenIntent != null) {
165            importance = IMPORTANCE_MAX;
166        }
167
168        stats.naturalImportance = importance;
169        return importance;
170    }
171
172    // copy any notes that the ranking system may have made before the update
173    public void copyRankingInformation(NotificationRecord previous) {
174        mContactAffinity = previous.mContactAffinity;
175        mRecentlyIntrusive = previous.mRecentlyIntrusive;
176        mPackagePriority = previous.mPackagePriority;
177        mPackageVisibility = previous.mPackageVisibility;
178        mIntercept = previous.mIntercept;
179        mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
180        mCreationTimeMs = previous.mCreationTimeMs;
181        mVisibleSinceMs = previous.mVisibleSinceMs;
182        // Don't copy importance information or mGlobalSortKey, recompute them.
183    }
184
185    public Notification getNotification() { return sbn.getNotification(); }
186    public int getFlags() { return sbn.getNotification().flags; }
187    public UserHandle getUser() { return sbn.getUser(); }
188    public String getKey() { return sbn.getKey(); }
189    /** @deprecated Use {@link #getUser()} instead. */
190    public int getUserId() { return sbn.getUserId(); }
191
192    void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
193        final Notification notification = sbn.getNotification();
194        final Icon icon = notification.getSmallIcon();
195        String iconStr = String.valueOf(icon);
196        if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
197            iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
198        }
199        pw.println(prefix + this);
200        pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
201        pw.println(prefix + "  icon=" + iconStr);
202        pw.println(prefix + "  pri=" + notification.priority);
203        pw.println(prefix + "  key=" + sbn.getKey());
204        pw.println(prefix + "  seen=" + mIsSeen);
205        pw.println(prefix + "  groupKey=" + getGroupKey());
206        pw.println(prefix + "  contentIntent=" + notification.contentIntent);
207        pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
208        pw.println(prefix + "  tickerText=" + notification.tickerText);
209        pw.println(prefix + "  contentView=" + notification.contentView);
210        pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
211                notification.defaults, notification.flags));
212        pw.println(prefix + "  sound=" + notification.sound);
213        pw.println(prefix + "  audioStreamType=" + notification.audioStreamType);
214        pw.println(prefix + "  audioAttributes=" + notification.audioAttributes);
215        pw.println(prefix + String.format("  color=0x%08x", notification.color));
216        pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
217        pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
218                notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
219        if (notification.actions != null && notification.actions.length > 0) {
220            pw.println(prefix + "  actions={");
221            final int N = notification.actions.length;
222            for (int i=0; i<N; i++) {
223                final Notification.Action action = notification.actions[i];
224                if (action != null) {
225                    pw.println(String.format("%s    [%d] \"%s\" -> %s",
226                            prefix,
227                            i,
228                            action.title,
229                            action.actionIntent == null ? "null" : action.actionIntent.toString()
230                    ));
231                }
232            }
233            pw.println(prefix + "  }");
234        }
235        if (notification.extras != null && notification.extras.size() > 0) {
236            pw.println(prefix + "  extras={");
237            for (String key : notification.extras.keySet()) {
238                pw.print(prefix + "    " + key + "=");
239                Object val = notification.extras.get(key);
240                if (val == null) {
241                    pw.println("null");
242                } else {
243                    pw.print(val.getClass().getSimpleName());
244                    if (redact && (val instanceof CharSequence || val instanceof String)) {
245                        // redact contents from bugreports
246                    } else if (val instanceof Bitmap) {
247                        pw.print(String.format(" (%dx%d)",
248                                ((Bitmap) val).getWidth(),
249                                ((Bitmap) val).getHeight()));
250                    } else if (val.getClass().isArray()) {
251                        final int N = Array.getLength(val);
252                        pw.print(" (" + N + ")");
253                        if (!redact) {
254                            for (int j=0; j<N; j++) {
255                                pw.println();
256                                pw.print(String.format("%s      [%d] %s",
257                                        prefix, j, String.valueOf(Array.get(val, j))));
258                            }
259                        }
260                    } else {
261                        pw.print(" (" + String.valueOf(val) + ")");
262                    }
263                    pw.println();
264                }
265            }
266            pw.println(prefix + "  }");
267        }
268        pw.println(prefix + "  stats=" + stats.toString());
269        pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
270        pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
271        pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
272        pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
273        pw.println(prefix + "  mUserImportance="
274                + NotificationListenerService.Ranking.importanceToString(mUserImportance));
275        pw.println(prefix + "  mImportance="
276                + NotificationListenerService.Ranking.importanceToString(mImportance));
277        pw.println(prefix + "  mImportanceExplanation=" + mImportanceExplanation);
278        pw.println(prefix + "  mIntercept=" + mIntercept);
279        pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
280        pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
281        pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
282        pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
283        pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
284        pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
285    }
286
287
288    static String idDebugString(Context baseContext, String packageName, int id) {
289        Context c;
290
291        if (packageName != null) {
292            try {
293                c = baseContext.createPackageContext(packageName, 0);
294            } catch (NameNotFoundException e) {
295                c = baseContext;
296            }
297        } else {
298            c = baseContext;
299        }
300
301        Resources r = c.getResources();
302        try {
303            return r.getResourceName(id);
304        } catch (Resources.NotFoundException e) {
305            return "<name unknown>";
306        }
307    }
308
309    @Override
310    public final String toString() {
311        return String.format(
312                "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s: %s)",
313                System.identityHashCode(this),
314                this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
315                this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
316                this.sbn.getNotification());
317    }
318
319    public void setContactAffinity(float contactAffinity) {
320        mContactAffinity = contactAffinity;
321        if (mImportance < IMPORTANCE_DEFAULT &&
322                mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
323            setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
324        }
325    }
326
327    public float getContactAffinity() {
328        return mContactAffinity;
329    }
330
331    public void setRecentlyIntrusive(boolean recentlyIntrusive) {
332        mRecentlyIntrusive = recentlyIntrusive;
333    }
334
335    public boolean isRecentlyIntrusive() {
336        return mRecentlyIntrusive;
337    }
338
339    public void setPackagePriority(int packagePriority) {
340        mPackagePriority = packagePriority;
341    }
342
343    public int getPackagePriority() {
344        return mPackagePriority;
345    }
346
347    public void setPackageVisibilityOverride(int packageVisibility) {
348        mPackageVisibility = packageVisibility;
349    }
350
351    public int getPackageVisibilityOverride() {
352        return mPackageVisibility;
353    }
354
355    public void setUserImportance(int importance) {
356        mUserImportance = importance;
357        applyUserImportance();
358    }
359
360    private String getUserExplanation() {
361        if (mUserExplanation == null) {
362            mUserExplanation =
363                    mContext.getString(com.android.internal.R.string.importance_from_user);
364        }
365        return mUserExplanation;
366    }
367
368    private String getPeopleExplanation() {
369        if (mPeopleExplanation == null) {
370            mPeopleExplanation =
371                    mContext.getString(com.android.internal.R.string.importance_from_person);
372        }
373        return mPeopleExplanation;
374    }
375
376    private void applyUserImportance() {
377        if (mUserImportance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
378            mImportance = mUserImportance;
379            mImportanceExplanation = getUserExplanation();
380        }
381    }
382
383    public int getUserImportance() {
384        return mUserImportance;
385    }
386
387    public void setImportance(int importance, CharSequence explanation) {
388        if (importance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
389            mImportance = importance;
390            mImportanceExplanation = explanation;
391        }
392        applyUserImportance();
393    }
394
395    public int getImportance() {
396        return mImportance;
397    }
398
399    public CharSequence getImportanceExplanation() {
400        return mImportanceExplanation;
401    }
402
403    public boolean setIntercepted(boolean intercept) {
404        mIntercept = intercept;
405        return mIntercept;
406    }
407
408    public boolean isIntercepted() {
409        return mIntercept;
410    }
411
412    public void setSuppressedVisualEffects(int effects) {
413        mSuppressedVisualEffects = effects;
414    }
415
416    public int getSuppressedVisualEffects() {
417        return mSuppressedVisualEffects;
418    }
419
420    public boolean isCategory(String category) {
421        return Objects.equals(getNotification().category, category);
422    }
423
424    public boolean isAudioStream(int stream) {
425        return getNotification().audioStreamType == stream;
426    }
427
428    public boolean isAudioAttributesUsage(int usage) {
429        final AudioAttributes attributes = getNotification().audioAttributes;
430        return attributes != null && attributes.getUsage() == usage;
431    }
432
433    /**
434     * Returns the timestamp to use for time-based sorting in the ranker.
435     */
436    public long getRankingTimeMs() {
437        return mRankingTimeMs;
438    }
439
440    /**
441     * @param now this current time in milliseconds.
442     * @returns the number of milliseconds since the most recent update, or the post time if none.
443     */
444    public int getFreshnessMs(long now) {
445        return (int) (now - mUpdateTimeMs);
446    }
447
448    /**
449     * @param now this current time in milliseconds.
450     * @returns the number of milliseconds since the the first post, ignoring updates.
451     */
452    public int getLifespanMs(long now) {
453        return (int) (now - mCreationTimeMs);
454    }
455
456    /**
457     * @param now this current time in milliseconds.
458     * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
459     */
460    public int getExposureMs(long now) {
461        return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
462    }
463
464    /**
465     * Set the visibility of the notification.
466     */
467    public void setVisibility(boolean visible, int rank) {
468        final long now = System.currentTimeMillis();
469        mVisibleSinceMs = visible ? now : mVisibleSinceMs;
470        stats.onVisibilityChanged(visible);
471        EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
472                (int) (now - mCreationTimeMs),
473                (int) (now - mUpdateTimeMs),
474                0, // exposure time
475                rank);
476    }
477
478    /**
479     * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
480     *     of the previous notification record, 0 otherwise
481     */
482    private long calculateRankingTimeMs(long previousRankingTimeMs) {
483        Notification n = getNotification();
484        // Take developer provided 'when', unless it's in the future.
485        if (n.when != 0 && n.when <= sbn.getPostTime()) {
486            return n.when;
487        }
488        // If we've ranked a previous instance with a timestamp, inherit it. This case is
489        // important in order to have ranking stability for updating notifications.
490        if (previousRankingTimeMs > 0) {
491            return previousRankingTimeMs;
492        }
493        return sbn.getPostTime();
494    }
495
496    public void setGlobalSortKey(String globalSortKey) {
497        mGlobalSortKey = globalSortKey;
498    }
499
500    public String getGlobalSortKey() {
501        return mGlobalSortKey;
502    }
503
504    /** Check if any of the listeners have marked this notification as seen by the user. */
505    public boolean isSeen() {
506        return mIsSeen;
507    }
508
509    /** Mark the notification as seen by the user. */
510    public void setSeen() {
511        mIsSeen = true;
512    }
513
514    public void setAuthoritativeRank(int authoritativeRank) {
515        mAuthoritativeRank = authoritativeRank;
516    }
517
518    public int getAuthoritativeRank() {
519        return mAuthoritativeRank;
520    }
521
522    public String getGroupKey() {
523        return sbn.getGroupKey();
524    }
525
526    public boolean isImportanceFromUser() {
527        return mImportance == mUserImportance;
528    }
529}
530