NotificationRecord.java revision 365e4c38d58d38bb61d1fdd870346f2f594825fd
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 android.app.Notification;
19import android.content.Context;
20import android.content.pm.PackageManager.NameNotFoundException;
21import android.content.res.Resources;
22import android.graphics.Bitmap;
23import android.media.AudioAttributes;
24import android.os.UserHandle;
25import android.service.notification.StatusBarNotification;
26
27import com.android.internal.annotations.VisibleForTesting;
28
29import java.io.PrintWriter;
30import java.lang.reflect.Array;
31import java.util.Arrays;
32import java.util.Objects;
33
34/**
35 * Holds data about notifications that should not be shared with the
36 * {@link android.service.notification.NotificationListenerService}s.
37 *
38 * <p>These objects should not be mutated unless the code is synchronized
39 * on {@link NotificationManagerService#mNotificationList}, and any
40 * modification should be followed by a sorting of that list.</p>
41 *
42 * <p>Is sortable by {@link NotificationComparator}.</p>
43 *
44 * {@hide}
45 */
46public final class NotificationRecord {
47    final StatusBarNotification sbn;
48    final int mOriginalFlags;
49
50    NotificationUsageStats.SingleNotificationStats stats;
51    boolean isCanceled;
52    int score;
53
54    // These members are used by NotificationSignalExtractors
55    // to communicate with the ranking module.
56    private float mContactAffinity;
57    private boolean mRecentlyIntrusive;
58
59    // is this notification currently being intercepted by Zen Mode?
60    private boolean mIntercept;
61
62    // The timestamp used for ranking.
63    private long mRankingTimeMs;
64
65    // Is this record an update of an old record?
66    public boolean isUpdate;
67    private int mPackagePriority;
68
69    private int mAuthoritativeRank;
70    private String mGlobalSortKey;
71    private int mPackageVisibility;
72
73    @VisibleForTesting
74    public NotificationRecord(StatusBarNotification sbn, int score)
75    {
76        this.sbn = sbn;
77        this.score = score;
78        mOriginalFlags = sbn.getNotification().flags;
79        mRankingTimeMs = calculateRankingTimeMs(0L);
80    }
81
82    // copy any notes that the ranking system may have made before the update
83    public void copyRankingInformation(NotificationRecord previous) {
84        mContactAffinity = previous.mContactAffinity;
85        mRecentlyIntrusive = previous.mRecentlyIntrusive;
86        mPackagePriority = previous.mPackagePriority;
87        mPackageVisibility = previous.mPackageVisibility;
88        mIntercept = previous.mIntercept;
89        mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
90        // Don't copy mGlobalSortKey, recompute it.
91    }
92
93    public Notification getNotification() { return sbn.getNotification(); }
94    public int getFlags() { return sbn.getNotification().flags; }
95    public UserHandle getUser() { return sbn.getUser(); }
96    public String getKey() { return sbn.getKey(); }
97    /** @deprecated Use {@link #getUser()} instead. */
98    public int getUserId() { return sbn.getUserId(); }
99
100    void dump(PrintWriter pw, String prefix, Context baseContext) {
101        final Notification notification = sbn.getNotification();
102        pw.println(prefix + this);
103        pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
104        pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
105                + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
106        pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
107        pw.println(prefix + "  key=" + sbn.getKey());
108        pw.println(prefix + "  groupKey=" + getGroupKey());
109        pw.println(prefix + "  contentIntent=" + notification.contentIntent);
110        pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
111        pw.println(prefix + "  tickerText=" + notification.tickerText);
112        pw.println(prefix + "  contentView=" + notification.contentView);
113        pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
114                notification.defaults, notification.flags));
115        pw.println(prefix + "  sound=" + notification.sound);
116        pw.println(prefix + "  audioStreamType=" + notification.audioStreamType);
117        pw.println(prefix + "  audioAttributes=" + notification.audioAttributes);
118        pw.println(prefix + String.format("  color=0x%08x", notification.color));
119        pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
120        pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
121                notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
122        if (notification.actions != null && notification.actions.length > 0) {
123            pw.println(prefix + "  actions={");
124            final int N = notification.actions.length;
125            for (int i=0; i<N; i++) {
126                final Notification.Action action = notification.actions[i];
127                pw.println(String.format("%s    [%d] \"%s\" -> %s",
128                        prefix,
129                        i,
130                        action.title,
131                        action.actionIntent.toString()
132                        ));
133            }
134            pw.println(prefix + "  }");
135        }
136        if (notification.extras != null && notification.extras.size() > 0) {
137            pw.println(prefix + "  extras={");
138            for (String key : notification.extras.keySet()) {
139                pw.print(prefix + "    " + key + "=");
140                Object val = notification.extras.get(key);
141                if (val == null) {
142                    pw.println("null");
143                } else {
144                    pw.print(val.getClass().getSimpleName());
145                    if (val instanceof CharSequence || val instanceof String) {
146                        // redact contents from bugreports
147                    } else if (val instanceof Bitmap) {
148                        pw.print(String.format(" (%dx%d)",
149                                ((Bitmap) val).getWidth(),
150                                ((Bitmap) val).getHeight()));
151                    } else if (val.getClass().isArray()) {
152                        final int N = Array.getLength(val);
153                        pw.println(" (" + N + ")");
154                    } else {
155                        pw.print(" (" + String.valueOf(val) + ")");
156                    }
157                    pw.println();
158                }
159            }
160            pw.println(prefix + "  }");
161        }
162        pw.println(prefix + "  stats=" + stats.toString());
163        pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
164        pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
165        pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
166        pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
167        pw.println(prefix + "  mIntercept=" + mIntercept);
168        pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
169        pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
170    }
171
172
173    static String idDebugString(Context baseContext, String packageName, int id) {
174        Context c;
175
176        if (packageName != null) {
177            try {
178                c = baseContext.createPackageContext(packageName, 0);
179            } catch (NameNotFoundException e) {
180                c = baseContext;
181            }
182        } else {
183            c = baseContext;
184        }
185
186        Resources r = c.getResources();
187        try {
188            return r.getResourceName(id);
189        } catch (Resources.NotFoundException e) {
190            return "<name unknown>";
191        }
192    }
193
194    @Override
195    public final String toString() {
196        return String.format(
197                "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d key=%s: %s)",
198                System.identityHashCode(this),
199                this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
200                this.sbn.getTag(), this.sbn.getScore(), this.sbn.getKey(),
201                this.sbn.getNotification());
202    }
203
204    public void setContactAffinity(float contactAffinity) {
205        mContactAffinity = contactAffinity;
206    }
207
208    public float getContactAffinity() {
209        return mContactAffinity;
210    }
211
212    public void setRecentlyIntusive(boolean recentlyIntrusive) {
213        mRecentlyIntrusive = recentlyIntrusive;
214    }
215
216    public boolean isRecentlyIntrusive() {
217        return mRecentlyIntrusive;
218    }
219
220    public void setPackagePriority(int packagePriority) {
221        mPackagePriority = packagePriority;
222    }
223
224    public int getPackagePriority() {
225        return mPackagePriority;
226    }
227
228    public void setPackageVisibilityOverride(int packageVisibility) {
229        mPackageVisibility = packageVisibility;
230    }
231
232    public int getPackageVisibilityOverride() {
233        return mPackageVisibility;
234    }
235
236    public boolean setIntercepted(boolean intercept) {
237        mIntercept = intercept;
238        return mIntercept;
239    }
240
241    public boolean isIntercepted() {
242        return mIntercept;
243    }
244
245    public boolean isCategory(String category) {
246        return Objects.equals(getNotification().category, category);
247    }
248
249    public boolean isAudioStream(int stream) {
250        return getNotification().audioStreamType == stream;
251    }
252
253    public boolean isAudioAttributesUsage(int usage) {
254        final AudioAttributes attributes = getNotification().audioAttributes;
255        return attributes != null && attributes.getUsage() == usage;
256    }
257
258    /**
259     * Returns the timestamp to use for time-based sorting in the ranker.
260     */
261    public long getRankingTimeMs() {
262        return mRankingTimeMs;
263    }
264
265    /**
266     * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
267     *     of the previous notification record, 0 otherwise
268     */
269    private long calculateRankingTimeMs(long previousRankingTimeMs) {
270        Notification n = getNotification();
271        // Take developer provided 'when', unless it's in the future.
272        if (n.when != 0 && n.when <= sbn.getPostTime()) {
273            return n.when;
274        }
275        // If we've ranked a previous instance with a timestamp, inherit it. This case is
276        // important in order to have ranking stability for updating notifications.
277        if (previousRankingTimeMs > 0) {
278            return previousRankingTimeMs;
279        }
280        return sbn.getPostTime();
281    }
282
283    public void setGlobalSortKey(String globalSortKey) {
284        mGlobalSortKey = globalSortKey;
285    }
286
287    public String getGlobalSortKey() {
288        return mGlobalSortKey;
289    }
290
291    public void setAuthoritativeRank(int authoritativeRank) {
292        mAuthoritativeRank = authoritativeRank;
293    }
294
295    public int getAuthoritativeRank() {
296        return mAuthoritativeRank;
297    }
298
299    public String getGroupKey() {
300        return sbn.getGroupKey();
301    }
302}
303