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