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  */
16 package com.android.server.notification;
17 
18 import android.app.Notification;
19 import android.content.Context;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.media.AudioAttributes;
24 import android.os.UserHandle;
25 import android.service.notification.StatusBarNotification;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.io.PrintWriter;
30 import java.lang.reflect.Array;
31 import java.util.Arrays;
32 import 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  */
46 public 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
NotificationRecord(StatusBarNotification sbn, int score)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
copyRankingInformation(NotificationRecord previous)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 
getNotification()93     public Notification getNotification() { return sbn.getNotification(); }
getFlags()94     public int getFlags() { return sbn.getNotification().flags; }
getUser()95     public UserHandle getUser() { return sbn.getUser(); }
getKey()96     public String getKey() { return sbn.getKey(); }
97     /** @deprecated Use {@link #getUser()} instead. */
getUserId()98     public int getUserId() { return sbn.getUserId(); }
99 
dump(PrintWriter pw, String prefix, Context baseContext)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 
idDebugString(Context baseContext, String packageName, int id)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
toString()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 
setContactAffinity(float contactAffinity)204     public void setContactAffinity(float contactAffinity) {
205         mContactAffinity = contactAffinity;
206     }
207 
getContactAffinity()208     public float getContactAffinity() {
209         return mContactAffinity;
210     }
211 
setRecentlyIntusive(boolean recentlyIntrusive)212     public void setRecentlyIntusive(boolean recentlyIntrusive) {
213         mRecentlyIntrusive = recentlyIntrusive;
214     }
215 
isRecentlyIntrusive()216     public boolean isRecentlyIntrusive() {
217         return mRecentlyIntrusive;
218     }
219 
setPackagePriority(int packagePriority)220     public void setPackagePriority(int packagePriority) {
221         mPackagePriority = packagePriority;
222     }
223 
getPackagePriority()224     public int getPackagePriority() {
225         return mPackagePriority;
226     }
227 
setPackageVisibilityOverride(int packageVisibility)228     public void setPackageVisibilityOverride(int packageVisibility) {
229         mPackageVisibility = packageVisibility;
230     }
231 
getPackageVisibilityOverride()232     public int getPackageVisibilityOverride() {
233         return mPackageVisibility;
234     }
235 
setIntercepted(boolean intercept)236     public boolean setIntercepted(boolean intercept) {
237         mIntercept = intercept;
238         return mIntercept;
239     }
240 
isIntercepted()241     public boolean isIntercepted() {
242         return mIntercept;
243     }
244 
isCategory(String category)245     public boolean isCategory(String category) {
246         return Objects.equals(getNotification().category, category);
247     }
248 
isAudioStream(int stream)249     public boolean isAudioStream(int stream) {
250         return getNotification().audioStreamType == stream;
251     }
252 
isAudioAttributesUsage(int usage)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      */
getRankingTimeMs()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      */
calculateRankingTimeMs(long previousRankingTimeMs)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 
setGlobalSortKey(String globalSortKey)283     public void setGlobalSortKey(String globalSortKey) {
284         mGlobalSortKey = globalSortKey;
285     }
286 
getGlobalSortKey()287     public String getGlobalSortKey() {
288         return mGlobalSortKey;
289     }
290 
setAuthoritativeRank(int authoritativeRank)291     public void setAuthoritativeRank(int authoritativeRank) {
292         mAuthoritativeRank = authoritativeRank;
293     }
294 
getAuthoritativeRank()295     public int getAuthoritativeRank() {
296         return mAuthoritativeRank;
297     }
298 
getGroupKey()299     public String getGroupKey() {
300         return sbn.getGroupKey();
301     }
302 }
303