• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MIN;
19 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
20 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
21 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
22 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW;
23 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
24 
25 import android.app.Notification;
26 import android.content.Context;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.Icon;
31 import android.media.AudioAttributes;
32 import android.os.UserHandle;
33 import android.service.notification.NotificationListenerService;
34 import android.service.notification.StatusBarNotification;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.server.EventLogTags;
39 
40 import java.io.PrintWriter;
41 import java.lang.reflect.Array;
42 import java.util.Arrays;
43 import java.util.Objects;
44 
45 /**
46  * Holds data about notifications that should not be shared with the
47  * {@link android.service.notification.NotificationListenerService}s.
48  *
49  * <p>These objects should not be mutated unless the code is synchronized
50  * on {@link NotificationManagerService#mNotificationList}, and any
51  * modification should be followed by a sorting of that list.</p>
52  *
53  * <p>Is sortable by {@link NotificationComparator}.</p>
54  *
55  * {@hide}
56  */
57 public final class NotificationRecord {
58     static final String TAG = "NotificationRecord";
59     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
60     final StatusBarNotification sbn;
61     final int mOriginalFlags;
62     private final Context mContext;
63 
64     NotificationUsageStats.SingleNotificationStats stats;
65     boolean isCanceled;
66     /** Whether the notification was seen by the user via one of the notification listeners. */
67     boolean mIsSeen;
68 
69     // These members are used by NotificationSignalExtractors
70     // to communicate with the ranking module.
71     private float mContactAffinity;
72     private boolean mRecentlyIntrusive;
73 
74     // is this notification currently being intercepted by Zen Mode?
75     private boolean mIntercept;
76 
77     // The timestamp used for ranking.
78     private long mRankingTimeMs;
79 
80     // The first post time, stable across updates.
81     private long mCreationTimeMs;
82 
83     // The most recent visibility event.
84     private long mVisibleSinceMs;
85 
86     // The most recent update time, or the creation time if no updates.
87     private long mUpdateTimeMs;
88 
89     // Is this record an update of an old record?
90     public boolean isUpdate;
91     private int mPackagePriority;
92 
93     private int mAuthoritativeRank;
94     private String mGlobalSortKey;
95     private int mPackageVisibility;
96     private int mUserImportance = IMPORTANCE_UNSPECIFIED;
97     private int mImportance = IMPORTANCE_UNSPECIFIED;
98     private CharSequence mImportanceExplanation = null;
99 
100     private int mSuppressedVisualEffects = 0;
101     private String mUserExplanation;
102     private String mPeopleExplanation;
103 
104     @VisibleForTesting
NotificationRecord(Context context, StatusBarNotification sbn)105     public NotificationRecord(Context context, StatusBarNotification sbn)
106     {
107         this.sbn = sbn;
108         mOriginalFlags = sbn.getNotification().flags;
109         mRankingTimeMs = calculateRankingTimeMs(0L);
110         mCreationTimeMs = sbn.getPostTime();
111         mUpdateTimeMs = mCreationTimeMs;
112         mContext = context;
113         stats = new NotificationUsageStats.SingleNotificationStats();
114         mImportance = defaultImportance();
115     }
116 
defaultImportance()117     private int defaultImportance() {
118         final Notification n = sbn.getNotification();
119         int importance = IMPORTANCE_DEFAULT;
120 
121         // Migrate notification flags to scores
122         if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
123             n.priority = Notification.PRIORITY_MAX;
124         }
125 
126         switch (n.priority) {
127             case Notification.PRIORITY_MIN:
128                 importance = IMPORTANCE_MIN;
129                 break;
130             case Notification.PRIORITY_LOW:
131                 importance = IMPORTANCE_LOW;
132                 break;
133             case Notification.PRIORITY_DEFAULT:
134                 importance = IMPORTANCE_DEFAULT;
135                 break;
136             case Notification.PRIORITY_HIGH:
137                 importance = IMPORTANCE_HIGH;
138                 break;
139             case Notification.PRIORITY_MAX:
140                 importance = IMPORTANCE_MAX;
141                 break;
142         }
143         stats.requestedImportance = importance;
144 
145         boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0
146                 || (n.defaults & Notification.DEFAULT_VIBRATE) != 0
147                 || n.sound != null
148                 || n.vibrate != null;
149         stats.isNoisy = isNoisy;
150 
151         if (!isNoisy && importance > IMPORTANCE_LOW) {
152             importance = IMPORTANCE_LOW;
153         }
154 
155         if (isNoisy) {
156             if (importance < IMPORTANCE_DEFAULT) {
157                 importance = IMPORTANCE_DEFAULT;
158             }
159         }
160 
161         if (n.fullScreenIntent != null) {
162             importance = IMPORTANCE_MAX;
163         }
164 
165         stats.naturalImportance = importance;
166         return importance;
167     }
168 
169     // copy any notes that the ranking system may have made before the update
copyRankingInformation(NotificationRecord previous)170     public void copyRankingInformation(NotificationRecord previous) {
171         mContactAffinity = previous.mContactAffinity;
172         mRecentlyIntrusive = previous.mRecentlyIntrusive;
173         mPackagePriority = previous.mPackagePriority;
174         mPackageVisibility = previous.mPackageVisibility;
175         mIntercept = previous.mIntercept;
176         mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
177         mCreationTimeMs = previous.mCreationTimeMs;
178         mVisibleSinceMs = previous.mVisibleSinceMs;
179         if (previous.sbn.getOverrideGroupKey() != null && !sbn.isAppGroup()) {
180             sbn.setOverrideGroupKey(previous.sbn.getOverrideGroupKey());
181         }
182         // Don't copy importance information or mGlobalSortKey, recompute them.
183     }
184 
getNotification()185     public Notification getNotification() { return sbn.getNotification(); }
getFlags()186     public int getFlags() { return sbn.getNotification().flags; }
getUser()187     public UserHandle getUser() { return sbn.getUser(); }
getKey()188     public String getKey() { return sbn.getKey(); }
189     /** @deprecated Use {@link #getUser()} instead. */
getUserId()190     public int getUserId() { return sbn.getUserId(); }
191 
dump(PrintWriter pw, String prefix, Context baseContext, boolean redact)192     void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
193         final Notification notification = sbn.getNotification();
194         final Icon icon = notification.getSmallIcon();
195         String iconStr = String.valueOf(icon);
196         if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
197             iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
198         }
199         pw.println(prefix + this);
200         pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
201         pw.println(prefix + "  icon=" + iconStr);
202         pw.println(prefix + "  pri=" + notification.priority);
203         pw.println(prefix + "  key=" + sbn.getKey());
204         pw.println(prefix + "  seen=" + mIsSeen);
205         pw.println(prefix + "  groupKey=" + getGroupKey());
206         pw.println(prefix + "  contentIntent=" + notification.contentIntent);
207         pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
208         pw.println(prefix + "  tickerText=" + notification.tickerText);
209         pw.println(prefix + "  contentView=" + notification.contentView);
210         pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
211                 notification.defaults, notification.flags));
212         pw.println(prefix + "  sound=" + notification.sound);
213         pw.println(prefix + "  audioStreamType=" + notification.audioStreamType);
214         pw.println(prefix + "  audioAttributes=" + notification.audioAttributes);
215         pw.println(prefix + String.format("  color=0x%08x", notification.color));
216         pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
217         pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
218                 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
219         if (notification.actions != null && notification.actions.length > 0) {
220             pw.println(prefix + "  actions={");
221             final int N = notification.actions.length;
222             for (int i=0; i<N; i++) {
223                 final Notification.Action action = notification.actions[i];
224                 if (action != null) {
225                     pw.println(String.format("%s    [%d] \"%s\" -> %s",
226                             prefix,
227                             i,
228                             action.title,
229                             action.actionIntent == null ? "null" : action.actionIntent.toString()
230                     ));
231                 }
232             }
233             pw.println(prefix + "  }");
234         }
235         if (notification.extras != null && notification.extras.size() > 0) {
236             pw.println(prefix + "  extras={");
237             for (String key : notification.extras.keySet()) {
238                 pw.print(prefix + "    " + key + "=");
239                 Object val = notification.extras.get(key);
240                 if (val == null) {
241                     pw.println("null");
242                 } else {
243                     pw.print(val.getClass().getSimpleName());
244                     if (redact && (val instanceof CharSequence || val instanceof String)) {
245                         // redact contents from bugreports
246                     } else if (val instanceof Bitmap) {
247                         pw.print(String.format(" (%dx%d)",
248                                 ((Bitmap) val).getWidth(),
249                                 ((Bitmap) val).getHeight()));
250                     } else if (val.getClass().isArray()) {
251                         final int N = Array.getLength(val);
252                         pw.print(" (" + N + ")");
253                         if (!redact) {
254                             for (int j=0; j<N; j++) {
255                                 pw.println();
256                                 pw.print(String.format("%s      [%d] %s",
257                                         prefix, j, String.valueOf(Array.get(val, j))));
258                             }
259                         }
260                     } else {
261                         pw.print(" (" + String.valueOf(val) + ")");
262                     }
263                     pw.println();
264                 }
265             }
266             pw.println(prefix + "  }");
267         }
268         pw.println(prefix + "  stats=" + stats.toString());
269         pw.println(prefix + "  mContactAffinity=" + mContactAffinity);
270         pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
271         pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
272         pw.println(prefix + "  mPackageVisibility=" + mPackageVisibility);
273         pw.println(prefix + "  mUserImportance="
274                 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
275         pw.println(prefix + "  mImportance="
276                 + NotificationListenerService.Ranking.importanceToString(mImportance));
277         pw.println(prefix + "  mImportanceExplanation=" + mImportanceExplanation);
278         pw.println(prefix + "  mIntercept=" + mIntercept);
279         pw.println(prefix + "  mGlobalSortKey=" + mGlobalSortKey);
280         pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
281         pw.println(prefix + "  mCreationTimeMs=" + mCreationTimeMs);
282         pw.println(prefix + "  mVisibleSinceMs=" + mVisibleSinceMs);
283         pw.println(prefix + "  mUpdateTimeMs=" + mUpdateTimeMs);
284         pw.println(prefix + "  mSuppressedVisualEffects= " + mSuppressedVisualEffects);
285     }
286 
287 
idDebugString(Context baseContext, String packageName, int id)288     static String idDebugString(Context baseContext, String packageName, int id) {
289         Context c;
290 
291         if (packageName != null) {
292             try {
293                 c = baseContext.createPackageContext(packageName, 0);
294             } catch (NameNotFoundException e) {
295                 c = baseContext;
296             }
297         } else {
298             c = baseContext;
299         }
300 
301         Resources r = c.getResources();
302         try {
303             return r.getResourceName(id);
304         } catch (Resources.NotFoundException e) {
305             return "<name unknown>";
306         }
307     }
308 
309     @Override
toString()310     public final String toString() {
311         return String.format(
312                 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s: %s)",
313                 System.identityHashCode(this),
314                 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
315                 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
316                 this.sbn.getNotification());
317     }
318 
setContactAffinity(float contactAffinity)319     public void setContactAffinity(float contactAffinity) {
320         mContactAffinity = contactAffinity;
321         if (mImportance < IMPORTANCE_DEFAULT &&
322                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
323             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
324         }
325     }
326 
getContactAffinity()327     public float getContactAffinity() {
328         return mContactAffinity;
329     }
330 
setRecentlyIntrusive(boolean recentlyIntrusive)331     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
332         mRecentlyIntrusive = recentlyIntrusive;
333     }
334 
isRecentlyIntrusive()335     public boolean isRecentlyIntrusive() {
336         return mRecentlyIntrusive;
337     }
338 
setPackagePriority(int packagePriority)339     public void setPackagePriority(int packagePriority) {
340         mPackagePriority = packagePriority;
341     }
342 
getPackagePriority()343     public int getPackagePriority() {
344         return mPackagePriority;
345     }
346 
setPackageVisibilityOverride(int packageVisibility)347     public void setPackageVisibilityOverride(int packageVisibility) {
348         mPackageVisibility = packageVisibility;
349     }
350 
getPackageVisibilityOverride()351     public int getPackageVisibilityOverride() {
352         return mPackageVisibility;
353     }
354 
setUserImportance(int importance)355     public void setUserImportance(int importance) {
356         mUserImportance = importance;
357         applyUserImportance();
358     }
359 
getUserExplanation()360     private String getUserExplanation() {
361         if (mUserExplanation == null) {
362             mUserExplanation =
363                     mContext.getString(com.android.internal.R.string.importance_from_user);
364         }
365         return mUserExplanation;
366     }
367 
getPeopleExplanation()368     private String getPeopleExplanation() {
369         if (mPeopleExplanation == null) {
370             mPeopleExplanation =
371                     mContext.getString(com.android.internal.R.string.importance_from_person);
372         }
373         return mPeopleExplanation;
374     }
375 
applyUserImportance()376     private void applyUserImportance() {
377         if (mUserImportance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
378             mImportance = mUserImportance;
379             mImportanceExplanation = getUserExplanation();
380         }
381     }
382 
getUserImportance()383     public int getUserImportance() {
384         return mUserImportance;
385     }
386 
setImportance(int importance, CharSequence explanation)387     public void setImportance(int importance, CharSequence explanation) {
388         if (importance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
389             mImportance = importance;
390             mImportanceExplanation = explanation;
391         }
392         applyUserImportance();
393     }
394 
getImportance()395     public int getImportance() {
396         return mImportance;
397     }
398 
getImportanceExplanation()399     public CharSequence getImportanceExplanation() {
400         return mImportanceExplanation;
401     }
402 
setIntercepted(boolean intercept)403     public boolean setIntercepted(boolean intercept) {
404         mIntercept = intercept;
405         return mIntercept;
406     }
407 
isIntercepted()408     public boolean isIntercepted() {
409         return mIntercept;
410     }
411 
setSuppressedVisualEffects(int effects)412     public void setSuppressedVisualEffects(int effects) {
413         mSuppressedVisualEffects = effects;
414     }
415 
getSuppressedVisualEffects()416     public int getSuppressedVisualEffects() {
417         return mSuppressedVisualEffects;
418     }
419 
isCategory(String category)420     public boolean isCategory(String category) {
421         return Objects.equals(getNotification().category, category);
422     }
423 
isAudioStream(int stream)424     public boolean isAudioStream(int stream) {
425         return getNotification().audioStreamType == stream;
426     }
427 
isAudioAttributesUsage(int usage)428     public boolean isAudioAttributesUsage(int usage) {
429         final AudioAttributes attributes = getNotification().audioAttributes;
430         return attributes != null && attributes.getUsage() == usage;
431     }
432 
433     /**
434      * Returns the timestamp to use for time-based sorting in the ranker.
435      */
getRankingTimeMs()436     public long getRankingTimeMs() {
437         return mRankingTimeMs;
438     }
439 
440     /**
441      * @param now this current time in milliseconds.
442      * @returns the number of milliseconds since the most recent update, or the post time if none.
443      */
getFreshnessMs(long now)444     public int getFreshnessMs(long now) {
445         return (int) (now - mUpdateTimeMs);
446     }
447 
448     /**
449      * @param now this current time in milliseconds.
450      * @returns the number of milliseconds since the the first post, ignoring updates.
451      */
getLifespanMs(long now)452     public int getLifespanMs(long now) {
453         return (int) (now - mCreationTimeMs);
454     }
455 
456     /**
457      * @param now this current time in milliseconds.
458      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
459      */
getExposureMs(long now)460     public int getExposureMs(long now) {
461         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
462     }
463 
464     /**
465      * Set the visibility of the notification.
466      */
setVisibility(boolean visible, int rank)467     public void setVisibility(boolean visible, int rank) {
468         final long now = System.currentTimeMillis();
469         mVisibleSinceMs = visible ? now : mVisibleSinceMs;
470         stats.onVisibilityChanged(visible);
471         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
472                 (int) (now - mCreationTimeMs),
473                 (int) (now - mUpdateTimeMs),
474                 0, // exposure time
475                 rank);
476     }
477 
478     /**
479      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
480      *     of the previous notification record, 0 otherwise
481      */
calculateRankingTimeMs(long previousRankingTimeMs)482     private long calculateRankingTimeMs(long previousRankingTimeMs) {
483         Notification n = getNotification();
484         // Take developer provided 'when', unless it's in the future.
485         if (n.when != 0 && n.when <= sbn.getPostTime()) {
486             return n.when;
487         }
488         // If we've ranked a previous instance with a timestamp, inherit it. This case is
489         // important in order to have ranking stability for updating notifications.
490         if (previousRankingTimeMs > 0) {
491             return previousRankingTimeMs;
492         }
493         return sbn.getPostTime();
494     }
495 
setGlobalSortKey(String globalSortKey)496     public void setGlobalSortKey(String globalSortKey) {
497         mGlobalSortKey = globalSortKey;
498     }
499 
getGlobalSortKey()500     public String getGlobalSortKey() {
501         return mGlobalSortKey;
502     }
503 
504     /** Check if any of the listeners have marked this notification as seen by the user. */
isSeen()505     public boolean isSeen() {
506         return mIsSeen;
507     }
508 
509     /** Mark the notification as seen by the user. */
setSeen()510     public void setSeen() {
511         mIsSeen = true;
512     }
513 
setAuthoritativeRank(int authoritativeRank)514     public void setAuthoritativeRank(int authoritativeRank) {
515         mAuthoritativeRank = authoritativeRank;
516     }
517 
getAuthoritativeRank()518     public int getAuthoritativeRank() {
519         return mAuthoritativeRank;
520     }
521 
getGroupKey()522     public String getGroupKey() {
523         return sbn.getGroupKey();
524     }
525 
isImportanceFromUser()526     public boolean isImportanceFromUser() {
527         return mImportance == mUserImportance;
528     }
529 }
530