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