OSDN Git Service

Remove Notification Topics.
[android-x86/frameworks-base.git] / services / core / java / com / android / server / notification / NotificationRecord.java
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_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;
23
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;
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     final StatusBarNotification sbn;
59     final int mOriginalFlags;
60     private final Context mContext;
61
62     NotificationUsageStats.SingleNotificationStats stats;
63     boolean isCanceled;
64     /** Whether the notification was seen by the user via one of the notification listeners. */
65     boolean mIsSeen;
66
67     // These members are used by NotificationSignalExtractors
68     // to communicate with the ranking module.
69     private float mContactAffinity;
70     private boolean mRecentlyIntrusive;
71
72     // is this notification currently being intercepted by Zen Mode?
73     private boolean mIntercept;
74
75     // The timestamp used for ranking.
76     private long mRankingTimeMs;
77
78     // The first post time, stable across updates.
79     private long mCreationTimeMs;
80
81     // The most recent visibility event.
82     private long mVisibleSinceMs;
83
84     // The most recent update time, or the creation time if no updates.
85     private long mUpdateTimeMs;
86
87     // Is this record an update of an old record?
88     public boolean isUpdate;
89     private int mPackagePriority;
90
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;
97
98     private int mSuppressedVisualEffects = 0;
99     private String mUserExplanation;
100     private String mPeopleExplanation;
101
102     @VisibleForTesting
103     public NotificationRecord(Context context, StatusBarNotification sbn)
104     {
105         this.sbn = sbn;
106         mOriginalFlags = sbn.getNotification().flags;
107         mRankingTimeMs = calculateRankingTimeMs(0L);
108         mCreationTimeMs = sbn.getPostTime();
109         mUpdateTimeMs = mCreationTimeMs;
110         mContext = context;
111         stats = new NotificationUsageStats.SingleNotificationStats();
112         mImportance = defaultImportance();
113     }
114
115     private int defaultImportance() {
116         final Notification n = sbn.getNotification();
117         int importance = IMPORTANCE_DEFAULT;
118
119         // Migrate notification flags to scores
120         if (0 != (n.flags & Notification.FLAG_HIGH_PRIORITY)) {
121             n.priority = Notification.PRIORITY_MAX;
122         }
123
124         switch (n.priority) {
125             case Notification.PRIORITY_MIN:
126             case Notification.PRIORITY_LOW:
127                 importance = IMPORTANCE_LOW;
128                 break;
129             case Notification.PRIORITY_DEFAULT:
130                 importance = IMPORTANCE_DEFAULT;
131                 break;
132             case Notification.PRIORITY_HIGH:
133                 importance = IMPORTANCE_HIGH;
134                 break;
135             case Notification.PRIORITY_MAX:
136                 importance = IMPORTANCE_MAX;
137                 break;
138         }
139         stats.requestedImportance = importance;
140
141         boolean isNoisy = (n.defaults & Notification.DEFAULT_SOUND) != 0
142                 || (n.defaults & Notification.DEFAULT_VIBRATE) != 0
143                 || n.sound != null
144                 || n.vibrate != null;
145         stats.isNoisy = isNoisy;
146         if (!isNoisy && importance > IMPORTANCE_DEFAULT) {
147             importance = IMPORTANCE_DEFAULT;
148         }
149
150         try {
151             final ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
152                     sbn.getPackageName(), 0);
153             if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.N) {
154                 if (isNoisy) {
155                     if (importance >= IMPORTANCE_HIGH) {
156                         importance = IMPORTANCE_MAX;
157                     } else {
158                         importance = IMPORTANCE_HIGH;
159                     }
160                 }
161             }
162         } catch (NameNotFoundException e) {
163             // oh well.
164         }
165
166         if (n.fullScreenIntent != null) {
167             importance = IMPORTANCE_MAX;
168         }
169
170         stats.naturalImportance = importance;
171         return importance;
172     }
173
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.
188     }
189
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(); }
196
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());
203         }
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",
230                         prefix,
231                         i,
232                         action.title,
233                         action.actionIntent.toString()
234                         ));
235             }
236             pw.println(prefix + "  }");
237         }
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);
243                 if (val == null) {
244                     pw.println("null");
245                 } else {
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 + ")");
256                         if (!redact) {
257                             for (int j=0; j<N; j++) {
258                                 pw.println();
259                                 pw.print(String.format("%s      [%d] %s",
260                                         prefix, j, String.valueOf(Array.get(val, j))));
261                             }
262                         }
263                     } else {
264                         pw.print(" (" + String.valueOf(val) + ")");
265                     }
266                     pw.println();
267                 }
268             }
269             pw.println(prefix + "  }");
270         }
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);
288     }
289
290
291     static String idDebugString(Context baseContext, String packageName, int id) {
292         Context c;
293
294         if (packageName != null) {
295             try {
296                 c = baseContext.createPackageContext(packageName, 0);
297             } catch (NameNotFoundException e) {
298                 c = baseContext;
299             }
300         } else {
301             c = baseContext;
302         }
303
304         Resources r = c.getResources();
305         try {
306             return r.getResourceName(id);
307         } catch (Resources.NotFoundException e) {
308             return "<name unknown>";
309         }
310     }
311
312     @Override
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());
320     }
321
322     public void setContactAffinity(float contactAffinity) {
323         mContactAffinity = contactAffinity;
324         if (mImportance < IMPORTANCE_DEFAULT &&
325                 mContactAffinity > ValidateNotificationPeople.VALID_CONTACT) {
326             setImportance(IMPORTANCE_DEFAULT, getPeopleExplanation());
327         }
328     }
329
330     public float getContactAffinity() {
331         return mContactAffinity;
332     }
333
334     public void setRecentlyIntrusive(boolean recentlyIntrusive) {
335         mRecentlyIntrusive = recentlyIntrusive;
336     }
337
338     public boolean isRecentlyIntrusive() {
339         return mRecentlyIntrusive;
340     }
341
342     public void setPackagePriority(int packagePriority) {
343         mPackagePriority = packagePriority;
344     }
345
346     public int getPackagePriority() {
347         return mPackagePriority;
348     }
349
350     public void setPackageVisibilityOverride(int packageVisibility) {
351         mPackageVisibility = packageVisibility;
352     }
353
354     public int getPackageVisibilityOverride() {
355         return mPackageVisibility;
356     }
357
358     public void setUserImportance(int importance) {
359         mUserImportance = importance;
360         applyUserImportance();
361     }
362
363     private String getUserExplanation() {
364         if (mUserExplanation == null) {
365             mUserExplanation =
366                     mContext.getString(com.android.internal.R.string.importance_from_user);
367         }
368         return mUserExplanation;
369     }
370
371     private String getPeopleExplanation() {
372         if (mPeopleExplanation == null) {
373             mPeopleExplanation =
374                     mContext.getString(com.android.internal.R.string.importance_from_person);
375         }
376         return mPeopleExplanation;
377     }
378
379     private void applyUserImportance() {
380         if (mUserImportance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
381             mImportance = mUserImportance;
382             mImportanceExplanation = getUserExplanation();
383         }
384     }
385
386     public int getUserImportance() {
387         return mUserImportance;
388     }
389
390     public void setImportance(int importance, CharSequence explanation) {
391         if (importance != NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED) {
392             mImportance = importance;
393             mImportanceExplanation = explanation;
394         }
395         applyUserImportance();
396     }
397
398     public int getImportance() {
399         return mImportance;
400     }
401
402     public CharSequence getImportanceExplanation() {
403         return mImportanceExplanation;
404     }
405
406     public boolean setIntercepted(boolean intercept) {
407         mIntercept = intercept;
408         return mIntercept;
409     }
410
411     public boolean isIntercepted() {
412         return mIntercept;
413     }
414
415     public void setSuppressedVisualEffects(int effects) {
416         mSuppressedVisualEffects = effects;
417     }
418
419     public int getSuppressedVisualEffects() {
420         return mSuppressedVisualEffects;
421     }
422
423     public boolean isCategory(String category) {
424         return Objects.equals(getNotification().category, category);
425     }
426
427     public boolean isAudioStream(int stream) {
428         return getNotification().audioStreamType == stream;
429     }
430
431     public boolean isAudioAttributesUsage(int usage) {
432         final AudioAttributes attributes = getNotification().audioAttributes;
433         return attributes != null && attributes.getUsage() == usage;
434     }
435
436     /**
437      * Returns the timestamp to use for time-based sorting in the ranker.
438      */
439     public long getRankingTimeMs() {
440         return mRankingTimeMs;
441     }
442
443     /**
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.
446      */
447     public int getFreshnessMs(long now) {
448         return (int) (now - mUpdateTimeMs);
449     }
450
451     /**
452      * @param now this current time in milliseconds.
453      * @returns the number of milliseconds since the the first post, ignoring updates.
454      */
455     public int getLifespanMs(long now) {
456         return (int) (now - mCreationTimeMs);
457     }
458
459     /**
460      * @param now this current time in milliseconds.
461      * @returns the number of milliseconds since the most recent visibility event, or 0 if never.
462      */
463     public int getExposureMs(long now) {
464         return mVisibleSinceMs == 0 ? 0 : (int) (now - mVisibleSinceMs);
465     }
466
467     /**
468      * Set the visibility of the notification.
469      */
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),
477                 0, // exposure time
478                 rank);
479     }
480
481     /**
482      * @param previousRankingTimeMs for updated notifications, {@link #getRankingTimeMs()}
483      *     of the previous notification record, 0 otherwise
484      */
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()) {
489             return n.when;
490         }
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;
495         }
496         return sbn.getPostTime();
497     }
498
499     public void setGlobalSortKey(String globalSortKey) {
500         mGlobalSortKey = globalSortKey;
501     }
502
503     public String getGlobalSortKey() {
504         return mGlobalSortKey;
505     }
506
507     /** Check if any of the listeners have marked this notification as seen by the user. */
508     public boolean isSeen() {
509         return mIsSeen;
510     }
511
512     /** Mark the notification as seen by the user. */
513     public void setSeen() {
514         mIsSeen = true;
515     }
516
517     public void setAuthoritativeRank(int authoritativeRank) {
518         mAuthoritativeRank = authoritativeRank;
519     }
520
521     public int getAuthoritativeRank() {
522         return mAuthoritativeRank;
523     }
524
525     public String getGroupKey() {
526         return sbn.getGroupKey();
527     }
528
529     public boolean isImportanceFromUser() {
530         return mImportance == mUserImportance;
531     }
532 }