2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.server.notification;
18 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
19 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_DEFAULT;
20 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
21 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_LOW;
22 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_MAX;
24 import android.app.Notification;
25 import android.content.Context;
26 import android.content.pm.ApplicationInfo;
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.Build;
33 import android.os.UserHandle;
34 import android.service.notification.NotificationListenerService;
35 import android.service.notification.StatusBarNotification;
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.server.EventLogTags;
40 import java.io.PrintWriter;
41 import java.lang.reflect.Array;
42 import java.util.Arrays;
43 import java.util.Objects;
46 * Holds data about notifications that should not be shared with the
47 * {@link android.service.notification.NotificationListenerService}s.
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>
53 * <p>Is sortable by {@link NotificationComparator}.</p>
57 public final class NotificationRecord {
58 final StatusBarNotification sbn;
59 final int mOriginalFlags;
60 private final Context mContext;
62 NotificationUsageStats.SingleNotificationStats stats;
64 /** Whether the notification was seen by the user via one of the notification listeners. */
67 // These members are used by NotificationSignalExtractors
68 // to communicate with the ranking module.
69 private float mContactAffinity;
70 private boolean mRecentlyIntrusive;
72 // is this notification currently being intercepted by Zen Mode?
73 private boolean mIntercept;
75 // The timestamp used for ranking.
76 private long mRankingTimeMs;
78 // The first post time, stable across updates.
79 private long mCreationTimeMs;
81 // The most recent visibility event.
82 private long mVisibleSinceMs;
84 // The most recent update time, or the creation time if no updates.
85 private long mUpdateTimeMs;
87 // Is this record an update of an old record?
88 public boolean isUpdate;
89 private int mPackagePriority;
91 private int mAuthoritativeRank;
92 private String mGlobalSortKey;
93 private int mPackageVisibility;
94 private int mUserImportance = IMPORTANCE_UNSPECIFIED;
95 private int mImportance = IMPORTANCE_UNSPECIFIED;
96 private CharSequence mImportanceExplanation = null;
98 private int mSuppressedVisualEffects = 0;
99 private String mUserExplanation;
100 private String mPeopleExplanation;
103 public NotificationRecord(Context context, StatusBarNotification sbn)
106 mOriginalFlags = sbn.getNotification().flags;
107 mRankingTimeMs = calculateRankingTimeMs(0L);
108 mCreationTimeMs = sbn.getPostTime();
109 mUpdateTimeMs = mCreationTimeMs;
111 stats = new NotificationUsageStats.SingleNotificationStats();
112 mImportance = defaultImportance();
115 private int defaultImportance() {
116 final Notification n = sbn.getNotification();
117 int importance = IMPORTANCE_DEFAULT;
119 // Migrate notification flags to scores
120 if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
121 n.priority = Notification.PRIORITY_MAX;
124 switch (n.priority) {
125 case Notification.PRIORITY_MIN:
126 case Notification.PRIORITY_LOW:
127 importance = IMPORTANCE_LOW;
129 case Notification.PRIORITY_DEFAULT:
130 importance = IMPORTANCE_DEFAULT;
132 case Notification.PRIORITY_HIGH:
133 importance = IMPORTANCE_HIGH;
135 case Notification.PRIORITY_MAX:
136 importance = IMPORTANCE_MAX;
139 stats.requestedImportance = importance;
141 boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0
142 || (n.defaults & Notification.DEFAULT_VIBRATE) != 0
144 || n.vibrate != null;
145 stats.isNoisy = isNoisy;
146 if (!isNoisy && importance > IMPORTANCE_DEFAULT) {
147 importance = IMPORTANCE_DEFAULT;
151 final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
152 sbn.getPackageName(), 0);
153 if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.N) {
155 if (importance >= IMPORTANCE_HIGH) {
156 importance = IMPORTANCE_MAX;
158 importance = IMPORTANCE_HIGH;
162 } catch (NameNotFoundException e) {
166 if (n.fullScreenIntent != null) {
167 importance = IMPORTANCE_MAX;
170 stats.naturalImportance = importance;
174 // copy any notes that the ranking system may have made before the update
175 public void copyRankingInformation(NotificationRecord previous) {
176 mContactAffinity = previous.mContactAffinity;
177 mRecentlyIntrusive = previous.mRecentlyIntrusive;
178 mPackagePriority = previous.mPackagePriority;
179 mPackageVisibility = previous.mPackageVisibility;
180 mIntercept = previous.mIntercept;
181 mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
182 mCreationTimeMs = previous.mCreationTimeMs;
183 mVisibleSinceMs = previous.mVisibleSinceMs;
184 mUserImportance = previous.mUserImportance;
185 mImportance = previous.mImportance;
186 mImportanceExplanation = previous.mImportanceExplanation;
187 // Don't copy mGlobalSortKey, recompute it.
190 public Notification getNotification() { return sbn.getNotification(); }
191 public int getFlags() { return sbn.getNotification().flags; }
192 public UserHandle getUser() { return sbn.getUser(); }
193 public String getKey() { return sbn.getKey(); }
194 /** @deprecated Use {@link #getUser()} instead. */
195 public int getUserId() { return sbn.getUserId(); }
197 void dump(PrintWriter pw, String prefix, Context baseContext, boolean redact) {
198 final Notification notification = sbn.getNotification();
199 final Icon icon = notification.getSmallIcon();
200 String iconStr = String.valueOf(icon);
201 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
202 iconStr += " / " + idDebugString(baseContext, icon.getResPackage(), icon.getResId());
204 pw.println(prefix + this);
205 pw.println(prefix + " uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
206 pw.println(prefix + " icon=" + iconStr);
207 pw.println(prefix + " pri=" + notification.priority);
208 pw.println(prefix + " key=" + sbn.getKey());
209 pw.println(prefix + " seen=" + mIsSeen);
210 pw.println(prefix + " groupKey=" + getGroupKey());
211 pw.println(prefix + " contentIntent=" + notification.contentIntent);
212 pw.println(prefix + " deleteIntent=" + notification.deleteIntent);
213 pw.println(prefix + " tickerText=" + notification.tickerText);
214 pw.println(prefix + " contentView=" + notification.contentView);
215 pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x",
216 notification.defaults, notification.flags));
217 pw.println(prefix + " sound=" + notification.sound);
218 pw.println(prefix + " audioStreamType=" + notification.audioStreamType);
219 pw.println(prefix + " audioAttributes=" + notification.audioAttributes);
220 pw.println(prefix + String.format(" color=0x%08x", notification.color));
221 pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate));
222 pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d",
223 notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
224 if (notification.actions != null && notification.actions.length > 0) {
225 pw.println(prefix + " actions={");
226 final int N = notification.actions.length;
227 for (int i=0; i<N; i++) {
228 final Notification.Action action = notification.actions[i];
229 pw.println(String.format("%s [%d] \"%s\" -> %s",
233 action.actionIntent.toString()
236 pw.println(prefix + " }");
238 if (notification.extras != null && notification.extras.size() > 0) {
239 pw.println(prefix + " extras={");
240 for (String key : notification.extras.keySet()) {
241 pw.print(prefix + " " + key + "=");
242 Object val = notification.extras.get(key);
246 pw.print(val.getClass().getSimpleName());
247 if (redact && (val instanceof CharSequence || val instanceof String)) {
248 // redact contents from bugreports
249 } else if (val instanceof Bitmap) {
250 pw.print(String.format(" (%dx%d)",
251 ((Bitmap) val).getWidth(),
252 ((Bitmap) val).getHeight()));
253 } else if (val.getClass().isArray()) {
254 final int N = Array.getLength(val);
255 pw.print(" (" + N + ")");
257 for (int j=0; j<N; j++) {
259 pw.print(String.format("%s [%d] %s",
260 prefix, j, String.valueOf(Array.get(val, j))));
264 pw.print(" (" + String.valueOf(val) + ")");
269 pw.println(prefix + " }");
271 pw.println(prefix + " stats=" + stats.toString());
272 pw.println(prefix + " mContactAffinity=" + mContactAffinity);
273 pw.println(prefix + " mRecentlyIntrusive=" + mRecentlyIntrusive);
274 pw.println(prefix + " mPackagePriority=" + mPackagePriority);
275 pw.println(prefix + " mPackageVisibility=" + mPackageVisibility);
276 pw.println(prefix + " mUserImportance="
277 + NotificationListenerService.Ranking.importanceToString(mUserImportance));
278 pw.println(prefix + " mImportance="
279 + NotificationListenerService.Ranking.importanceToString(mImportance));
280 pw.println(prefix + " mImportanceExplanation=" + mImportanceExplanation);
281 pw.println(prefix + " mIntercept=" + mIntercept);
282 pw.println(prefix + " mGlobalSortKey=" + mGlobalSortKey);
283 pw.println(prefix + " mRankingTimeMs=" + mRankingTimeMs);
284 pw.println(prefix + " mCreationTimeMs=" + mCreationTimeMs);
285 pw.println(prefix + " mVisibleSinceMs=" + mVisibleSinceMs);
286 pw.println(prefix + " mUpdateTimeMs=" + mUpdateTimeMs);
287 pw.println(prefix + " mSuppressedVisualEffects= " + mSuppressedVisualEffects);
291 static String idDebugString(Context baseContext, String packageName, int id) {
294 if (packageName != null) {
296 c = baseContext.createPackageContext(packageName, 0);
297 } catch (NameNotFoundException e) {
304 Resources r = c.getResources();
306 return r.getResourceName(id);
307 } catch (Resources.NotFoundException e) {
308 return "<name unknown>";
313 public final String toString() {
314 return String.format(
315 "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s importance=%d key=%s: %s)",
316 System.identityHashCode(this),
317 this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
318 this.sbn.getTag(), this.mImportance, this.sbn.getKey(),
319 this.sbn.getNotification());
322 public void setContactAffinity(float contactAffinity) {
323 mContactAffinity = contactAffinity;
324 if (mImportance < IMPORTANCE_DEFAULT &&
325 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
326 setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
330 public float getContactAffinity() {
331 return mContactAffinity;
334 public void setRecentlyIntrusive(boolean recentlyIntrusive) {
335 mRecentlyIntrusive = recentlyIntrusive;
338 public boolean isRecentlyIntrusive() {
339 return mRecentlyIntrusive;
342 public void setPackagePriority(int packagePriority) {
343 mPackagePriority = packagePriority;
346 public int getPackagePriority() {
347 return mPackagePriority;
350 public void setPackageVisibilityOverride(int packageVisibility) {
351 mPackageVisibility = packageVisibility;
354 public int getPackageVisibilityOverride() {
355 return mPackageVisibility;
358 public void setUserImportance(int importance) {
359 mUserImportance = importance;
360 applyUserImportance();
363 private String getUserExplanation() {
364 if (mUserExplanation == null) {
366 mContext.getString(com.android.internal.R.string.importance_from_user);
368 return mUserExplanation;
371 private String getPeopleExplanation() {
372 if (mPeopleExplanation == null) {
374 mContext.getString(com.android.internal.R.string.importance_from_person);
376 return mPeopleExplanation;
379 private void applyUserImportance() {
380 if (mUserImportance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
381 mImportance = mUserImportance;
382 mImportanceExplanation = getUserExplanation();
386 public int getUserImportance() {
387 return mUserImportance;
390 public void setImportance(int importance, CharSequence explanation) {
391 if (importance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
392 mImportance = importance;
393 mImportanceExplanation = explanation;
395 applyUserImportance();
398 public int getImportance() {
402 public CharSequence getImportanceExplanation() {
403 return mImportanceExplanation;
406 public boolean setIntercepted(boolean intercept) {
407 mIntercept = intercept;
411 public boolean isIntercepted() {
415 public void setSuppressedVisualEffects(int effects) {
416 mSuppressedVisualEffects = effects;
419 public int getSuppressedVisualEffects() {
420 return mSuppressedVisualEffects;
423 public boolean isCategory(String category) {
424 return Objects.equals(getNotification().category, category);
427 public boolean isAudioStream(int stream) {
428 return getNotification().audioStreamType == stream;
431 public boolean isAudioAttributesUsage(int usage) {
432 final AudioAttributes attributes = getNotification().audioAttributes;
433 return attributes != null && attributes.getUsage() == usage;
437 * Returns the timestamp to use for time-based sorting in the ranker.
439 public long getRankingTimeMs() {
440 return mRankingTimeMs;
444 * @param now this current time in milliseconds.
445 * @returns the number of milliseconds since the most recent update, or the post time if none.
447 public int getFreshnessMs(long now) {
448 return (int) (now - mUpdateTimeMs);
452 * @param now this current time in milliseconds.
453 * @returns the number of milliseconds since the the first post, ignoring updates.
455 public int getLifespanMs(long now) {
456 return (int) (now - mCreationTimeMs);
460 * @param now this current time in milliseconds.
461 * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
463 public int getExposureMs(long now) {
464 return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
468 * Set the visibility of the notification.
470 public void setVisibility(boolean visible, int rank) {
471 final long now = System.currentTimeMillis();
472 mVisibleSinceMs = visible ? now : mVisibleSinceMs;
473 stats.onVisibilityChanged(visible);
474 EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
475 (int) (now - mCreationTimeMs),
476 (int) (now - mUpdateTimeMs),
482 * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
483 * of the previous notification record, 0 otherwise
485 private long calculateRankingTimeMs(long previousRankingTimeMs) {
486 Notification n = getNotification();
487 // Take developer provided 'when', unless it's in the future.
488 if (n.when != 0 && n.when <= sbn.getPostTime()) {
491 // If we've ranked a previous instance with a timestamp, inherit it. This case is
492 // important in order to have ranking stability for updating notifications.
493 if (previousRankingTimeMs > 0) {
494 return previousRankingTimeMs;
496 return sbn.getPostTime();
499 public void setGlobalSortKey(String globalSortKey) {
500 mGlobalSortKey = globalSortKey;
503 public String getGlobalSortKey() {
504 return mGlobalSortKey;
507 /** Check if any of the listeners have marked this notification as seen by the user. */
508 public boolean isSeen() {
512 /** Mark the notification as seen by the user. */
513 public void setSeen() {
517 public void setAuthoritativeRank(int authoritativeRank) {
518 mAuthoritativeRank = authoritativeRank;
521 public int getAuthoritativeRank() {
522 return mAuthoritativeRank;
525 public String getGroupKey() {
526 return sbn.getGroupKey();
529 public boolean isImportanceFromUser() {
530 return mImportance == mUserImportance;