• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package android.service.notification;
18 
19 import static android.text.TextUtils.formatSimple;
20 
21 import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif;
22 
23 import android.annotation.NonNull;
24 import android.app.Notification;
25 import android.app.NotificationManager;
26 import android.app.Person;
27 import android.compat.annotation.UnsupportedAppUsage;
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.metrics.LogMaker;
32 import android.os.Build;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import android.os.Trace;
36 import android.os.UserHandle;
37 import android.util.ArrayMap;
38 
39 import com.android.internal.logging.InstanceId;
40 import com.android.internal.logging.nano.MetricsProto;
41 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
42 
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.Map;
46 
47 /**
48  * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
49  * the status bar and any {@link android.service.notification.NotificationListenerService}s.
50  */
51 public class StatusBarNotification implements Parcelable {
52     static final int MAX_LOG_TAG_LENGTH = 36;
53 
54     @UnsupportedAppUsage
55     private final String pkg;
56     @UnsupportedAppUsage
57     private final int id;
58     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
59     private final String tag;
60     private final String key;
61     private String groupKey;
62     private String overrideGroupKey;
63 
64     @UnsupportedAppUsage
65     private final int uid;
66     private final String opPkg;
67     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
68     private final int initialPid;
69     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
70     private final Notification notification;
71     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
72     private final UserHandle user;
73     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
74     private final long postTime;
75     // A small per-notification ID, used for statsd logging.
76     private InstanceId mInstanceId;  // Not final, see setInstanceId()
77 
78     /**
79      * @deprecated This field is only used when
80      * {@link enablePerDisplayPackageContextCacheInStatusbarNotif}
81      * is disabled.
82      */
83     @Deprecated
84     private Context mContext; // used for inflation & icon expansion
85     // Maps display id to context used for remote view content inflation and status bar icon.
86     private final Map<Integer, Context> mContextForDisplayId =
87             Collections.synchronizedMap(new ArrayMap<>());
88 
89     /** @hide */
StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid, int initialPid, Notification notification, UserHandle user, String overrideGroupKey, long postTime)90     public StatusBarNotification(String pkg, String opPkg, int id,
91             String tag, int uid, int initialPid, Notification notification, UserHandle user,
92             String overrideGroupKey, long postTime) {
93         if (pkg == null) throw new NullPointerException();
94         if (notification == null) throw new NullPointerException();
95 
96         this.pkg = pkg;
97         this.opPkg = opPkg;
98         this.id = id;
99         this.tag = tag;
100         this.uid = uid;
101         this.initialPid = initialPid;
102         this.notification = notification;
103         this.user = user;
104         this.postTime = postTime;
105         this.overrideGroupKey = overrideGroupKey;
106         this.key = key();
107         this.groupKey = groupKey();
108     }
109 
110     /**
111      * @deprecated Non-system apps should not need to create StatusBarNotifications.
112      */
113     @Deprecated
StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid, int initialPid, int score, Notification notification, UserHandle user, long postTime)114     public StatusBarNotification(String pkg, String opPkg, int id, String tag, int uid,
115             int initialPid, int score, Notification notification, UserHandle user,
116             long postTime) {
117         if (pkg == null) throw new NullPointerException();
118         if (notification == null) throw new NullPointerException();
119 
120         this.pkg = pkg;
121         this.opPkg = opPkg;
122         this.id = id;
123         this.tag = tag;
124         this.uid = uid;
125         this.initialPid = initialPid;
126         this.notification = notification;
127         this.user = user;
128         this.postTime = postTime;
129         this.key = key();
130         this.groupKey = groupKey();
131     }
132 
StatusBarNotification(Parcel in)133     public StatusBarNotification(Parcel in) {
134         this.pkg = in.readString();
135         this.opPkg = in.readString();
136         this.id = in.readInt();
137         if (in.readInt() != 0) {
138             this.tag = in.readString();
139         } else {
140             this.tag = null;
141         }
142         this.uid = in.readInt();
143         this.initialPid = in.readInt();
144         this.notification = new Notification(in);
145         this.user = UserHandle.readFromParcel(in);
146         this.postTime = in.readLong();
147         if (in.readInt() != 0) {
148             this.overrideGroupKey = in.readString();
149         }
150         if (in.readInt() != 0) {
151             this.mInstanceId = InstanceId.CREATOR.createFromParcel(in);
152         }
153         this.key = key();
154         this.groupKey = groupKey();
155     }
156 
157     /**
158      * @hide
159      */
getUidFromKey(@onNull String key)160     public static int getUidFromKey(@NonNull String key) {
161         String[] parts = key.split("\\|");
162         if (parts.length >= 5) {
163             try {
164                 int uid = Integer.parseInt(parts[4]);
165                 return uid;
166             } catch (NumberFormatException e) {
167                 return -1;
168             }
169         }
170         return -1;
171     }
172 
173     /**
174      * @hide
175      */
getPkgFromKey(@onNull String key)176     public static String getPkgFromKey(@NonNull String key) {
177         String[] parts = key.split("\\|");
178         if (parts.length >= 2) {
179             return parts[1];
180         }
181         return null;
182     }
183 
key()184     private String key() {
185         String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
186         if (overrideGroupKey != null && getNotification().isGroupSummary()) {
187             sbnKey = sbnKey + "|" + overrideGroupKey;
188         }
189         return sbnKey;
190     }
191 
groupKey()192     private String groupKey() {
193         if (overrideGroupKey != null) {
194             if (Flags.notificationForceGrouping()) {
195                 return overrideGroupKey;
196             } else {
197                 return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
198             }
199         }
200         final String group = getNotification().getGroup();
201         final String sortKey = getNotification().getSortKey();
202         if (group == null && sortKey == null) {
203             // a group of one
204             return key;
205         }
206         return user.getIdentifier() + "|" + pkg + "|" +
207                 (group == null
208                         ? "c:" + notification.getChannelId()
209                         : "g:" + group);
210     }
211 
212     /**
213      * Returns true if this notification is part of a group.
214      */
isGroup()215     public boolean isGroup() {
216         if (overrideGroupKey != null || isAppGroup()) {
217             return true;
218         }
219         return false;
220     }
221 
222     /**
223      * Returns true if application asked that this notification be part of a group.
224      */
isAppGroup()225     public boolean isAppGroup() {
226         if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
227             return true;
228         }
229         return false;
230     }
231 
writeToParcel(Parcel out, int flags)232     public void writeToParcel(Parcel out, int flags) {
233         out.writeString(this.pkg);
234         out.writeString(this.opPkg);
235         out.writeInt(this.id);
236         if (this.tag != null) {
237             out.writeInt(1);
238             out.writeString(this.tag);
239         } else {
240             out.writeInt(0);
241         }
242         out.writeInt(this.uid);
243         out.writeInt(this.initialPid);
244         this.notification.writeToParcel(out, flags);
245         user.writeToParcel(out, flags);
246         out.writeLong(this.postTime);
247         if (this.overrideGroupKey != null) {
248             out.writeInt(1);
249             out.writeString(this.overrideGroupKey);
250         } else {
251             out.writeInt(0);
252         }
253         if (this.mInstanceId != null) {
254             out.writeInt(1);
255             mInstanceId.writeToParcel(out, flags);
256         } else {
257             out.writeInt(0);
258         }
259     }
260 
describeContents()261     public int describeContents() {
262         return 0;
263     }
264 
265     public static final @android.annotation.NonNull
266             Parcelable.Creator<StatusBarNotification> CREATOR =
267             new Parcelable.Creator<StatusBarNotification>() {
268                 public StatusBarNotification createFromParcel(Parcel parcel) {
269                     return new StatusBarNotification(parcel);
270                 }
271 
272             public StatusBarNotification[] newArray(int size) {
273                 return new StatusBarNotification[size];
274             }
275     };
276 
277     /**
278      * @hide
279      */
cloneLight()280     public StatusBarNotification cloneLight() {
281         final Notification no = new Notification();
282         this.notification.cloneInto(no, false); // light copy
283         return cloneShallow(no);
284     }
285 
286     @Override
clone()287     public StatusBarNotification clone() {
288         return cloneShallow(this.notification.clone());
289     }
290 
291     /**
292      * @param notification Some kind of clone of this.notification.
293      * @return A shallow copy of self, with notification in place of this.notification.
294      *
295      * @hide
296      */
cloneShallow(Notification notification)297     public StatusBarNotification cloneShallow(Notification notification) {
298         StatusBarNotification result = new StatusBarNotification(this.pkg, this.opPkg,
299                 this.id, this.tag, this.uid, this.initialPid,
300                 notification, this.user, this.overrideGroupKey, this.postTime);
301         result.setInstanceId(this.mInstanceId);
302         return result;
303     }
304 
305     @Override
toString()306     public String toString() {
307         return formatSimple(
308                 "StatusBarNotification(pkg=%s user=%s id=%d tag=%s key=%s: %s)",
309                 this.pkg, this.user, this.id, this.tag,
310                 this.key, this.notification);
311     }
312 
313     /**
314      * Convenience method to check the notification's flags for
315      * {@link Notification#FLAG_ONGOING_EVENT}.
316      */
isOngoing()317     public boolean isOngoing() {
318         return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
319     }
320 
321     /**
322      * @hide
323      *
324      * Convenience method to check the notification's flags for
325      * {@link Notification#FLAG_NO_DISMISS}.
326      */
isNonDismissable()327     public boolean isNonDismissable() {
328         return (notification.flags & Notification.FLAG_NO_DISMISS) != 0;
329     }
330 
331     /**
332      * Convenience method to check the notification's flags for
333      * either {@link Notification#FLAG_ONGOING_EVENT} or
334      * {@link Notification#FLAG_NO_CLEAR}.
335      */
isClearable()336     public boolean isClearable() {
337         return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
338                 && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
339     }
340 
341     /**
342      * Returns a userid for whom this notification is intended.
343      *
344      * @deprecated Use {@link #getUser()} instead.
345      */
346     @Deprecated
getUserId()347     public int getUserId() {
348         return this.user.getIdentifier();
349     }
350 
351     /**
352      * Like {@link #getUserId()} but handles special users.
353      * @hide
354      */
getNormalizedUserId()355     public int getNormalizedUserId() {
356         int userId = getUserId();
357         if (userId == UserHandle.USER_ALL) {
358             userId = UserHandle.USER_SYSTEM;
359         }
360         return userId;
361     }
362 
363     /** The package that the notification belongs to. */
getPackageName()364     public String getPackageName() {
365         return pkg;
366     }
367 
368     /** The id supplied to {@link android.app.NotificationManager#notify(int, Notification)}. */
getId()369     public int getId() {
370         return id;
371     }
372 
373     /**
374      * The tag supplied to {@link android.app.NotificationManager#notify(int, Notification)},
375      * or null if no tag was specified.
376      */
getTag()377     public String getTag() {
378         return tag;
379     }
380 
381     /**
382      * The notifying app's ({@link #getPackageName()}'s) uid.
383      */
getUid()384     public int getUid() {
385         return uid;
386     }
387 
388     /**
389      * The package that posted the notification.
390      * <p> Might be different from {@link #getPackageName()} if the app owning the notification has
391      * a {@link NotificationManager#setNotificationDelegate(String) notification delegate}.
392      */
getOpPkg()393     public @NonNull String getOpPkg() {
394         return opPkg;
395     }
396 
397     /** @hide */
398     @UnsupportedAppUsage
getInitialPid()399     public int getInitialPid() {
400         return initialPid;
401     }
402 
403     /**
404      * The {@link android.app.Notification} supplied to
405      * {@link android.app.NotificationManager#notify(int, Notification)}.
406      */
getNotification()407     public Notification getNotification() {
408         return notification;
409     }
410 
411     /**
412      * The {@link android.os.UserHandle} for whom this notification is intended.
413      */
getUser()414     public UserHandle getUser() {
415         return user;
416     }
417 
418     /**
419      * The time (in {@link System#currentTimeMillis} time) the notification was posted,
420      * which may be different than {@link android.app.Notification#when}.
421      */
getPostTime()422     public long getPostTime() {
423         return postTime;
424     }
425 
426     /**
427      * A unique instance key for this notification record.
428      */
getKey()429     public String getKey() {
430         return key;
431     }
432 
433     /**
434      * A key that indicates the group with which this message ranks.
435      */
getGroupKey()436     public String getGroupKey() {
437         return groupKey;
438     }
439 
440     /**
441      * The ID passed to setGroup(), or the override, or null.
442      *
443      * @hide
444      */
getGroup()445     public String getGroup() {
446         if (overrideGroupKey != null) {
447             return overrideGroupKey;
448         }
449         return getNotification().getGroup();
450     }
451 
452     /**
453      * Sets the override group key.
454      */
setOverrideGroupKey(String overrideGroupKey)455     public void setOverrideGroupKey(String overrideGroupKey) {
456         this.overrideGroupKey = overrideGroupKey;
457         groupKey = groupKey();
458     }
459 
460     /**
461      * Returns the override group key.
462      */
getOverrideGroupKey()463     public String getOverrideGroupKey() {
464         return overrideGroupKey;
465     }
466 
467     /**
468      * @hide
469      */
clearPackageContext()470     public void clearPackageContext() {
471         if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
472             mContextForDisplayId.clear();
473         } else {
474             mContext = null;
475         }
476     }
477 
478     /**
479      * @hide
480      */
getInstanceId()481     public InstanceId getInstanceId() {
482         return mInstanceId;
483     }
484 
485     /**
486      * @hide
487      */
setInstanceId(InstanceId instanceId)488     public void setInstanceId(InstanceId instanceId) {
489         mInstanceId = instanceId;
490     }
491 
492     /**
493      * @hide
494      */
495     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getPackageContext(Context context)496     public Context getPackageContext(Context context) {
497         if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
498             if (context == null) return null;
499             return mContextForDisplayId.computeIfAbsent(context.getDisplayId(),
500                     (displayId) -> createPackageContext(context));
501         } else {
502             if (mContext == null) {
503                 try {
504                     ApplicationInfo ai = context.getPackageManager()
505                             .getApplicationInfoAsUser(pkg,
506                                     PackageManager.MATCH_UNINSTALLED_PACKAGES,
507                                     getNormalizedUserId());
508                     mContext = context.createApplicationContext(ai,
509                             Context.CONTEXT_RESTRICTED);
510                 } catch (PackageManager.NameNotFoundException e) {
511                     mContext = null;
512                 }
513             }
514             if (mContext == null) {
515                 mContext = context;
516             }
517             return mContext;
518         }
519     }
520 
createPackageContext(Context context)521     private Context createPackageContext(Context context) {
522         try {
523             Trace.beginSection("StatusBarNotification#createPackageContext");
524             ApplicationInfo ai = context.getPackageManager()
525                     .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
526                             getNormalizedUserId());
527             return context.createApplicationContext(ai, Context.CONTEXT_RESTRICTED);
528         } catch (PackageManager.NameNotFoundException e) {
529             return context;
530         } finally {
531             Trace.endSection();
532         }
533     }
534 
535     /**
536      * Returns a LogMaker that contains all basic information of the notification.
537      *
538      * @hide
539      */
getLogMaker()540     public LogMaker getLogMaker() {
541         LogMaker logMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN).setPackageName(getPackageName())
542                 .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
543                 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
544                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag())
545                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
546                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
547                         getNotification().isGroupSummary() ? 1 : 0)
548                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CATEGORY,
549                         getNotification().category);
550         if (getNotification().extras != null) {
551             // Log the style used, if present.  We only log the hash here, as notification log
552             // events are frequent, while there are few styles (hence low chance of collisions).
553             String template = getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
554             if (template != null && !template.isEmpty()) {
555                 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE,
556                         template.hashCode());
557             }
558             ArrayList<Person> people = getNotification().extras.getParcelableArrayList(
559                     Notification.EXTRA_PEOPLE_LIST, android.app.Person.class);
560             if (people != null && !people.isEmpty()) {
561                 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_PEOPLE, people.size());
562             }
563         }
564         return logMaker;
565     }
566 
567     /**
568      * @hide
569      */
getShortcutId()570     public String getShortcutId() {
571         return getNotification().getShortcutId();
572     }
573 
574     /**
575      *  Returns a probably-unique string based on the notification's group name,
576      *  with no more than MAX_LOG_TAG_LENGTH characters.
577      * @return String based on group name of notification.
578      * @hide
579      */
getGroupLogTag()580     public String getGroupLogTag() {
581         return shortenTag(getGroup());
582     }
583 
584     /**
585      *  Returns a probably-unique string based on the notification's channel ID,
586      *  with no more than MAX_LOG_TAG_LENGTH characters.
587      * @return String based on channel ID of notification.
588      * @hide
589      */
getChannelIdLogTag()590     public String getChannelIdLogTag() {
591         if (notification.getChannelId() == null) {
592             return null;
593         }
594         return shortenTag(notification.getChannelId());
595     }
596 
597     // Make logTag with max size MAX_LOG_TAG_LENGTH.
598     // For shorter or equal tags, returns the tag.
599     // For longer tags, truncate the tag and append a hash of the full tag to
600     // fill the maximum size.
shortenTag(String logTag)601     private String shortenTag(String logTag) {
602         if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
603             return logTag;
604         }
605         String hash = Integer.toHexString(logTag.hashCode());
606         return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
607                 + hash;
608     }
609 }
610