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