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