• 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 
142     /**
143      * @hide
144      */
getUidFromKey(@onNull String key)145     public static int getUidFromKey(@NonNull String key) {
146         String[] parts = key.split("\\|");
147         if (parts.length >= 5) {
148             try {
149                 int uid = Integer.parseInt(parts[4]);
150                 return uid;
151             } catch (NumberFormatException e) {
152                 return -1;
153             }
154         }
155         return -1;
156     }
157 
158     /**
159      * @hide
160      */
getPkgFromKey(@onNull String key)161     public static String getPkgFromKey(@NonNull String key) {
162         String[] parts = key.split("\\|");
163         if (parts.length >= 2) {
164             return parts[1];
165         }
166         return null;
167     }
168 
key()169     private String key() {
170         String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
171         if (overrideGroupKey != null && getNotification().isGroupSummary()) {
172             sbnKey = sbnKey + "|" + overrideGroupKey;
173         }
174         return sbnKey;
175     }
176 
groupKey()177     private String groupKey() {
178         if (overrideGroupKey != null) {
179             return user.getIdentifier() + "|" + pkg + "|" + "g:" + overrideGroupKey;
180         }
181         final String group = getNotification().getGroup();
182         final String sortKey = getNotification().getSortKey();
183         if (group == null && sortKey == null) {
184             // a group of one
185             return key;
186         }
187         return user.getIdentifier() + "|" + pkg + "|" +
188                 (group == null
189                         ? "c:" + notification.getChannelId()
190                         : "g:" + group);
191     }
192 
193     /**
194      * Returns true if this notification is part of a group.
195      */
isGroup()196     public boolean isGroup() {
197         if (overrideGroupKey != null || isAppGroup()) {
198             return true;
199         }
200         return false;
201     }
202 
203     /**
204      * Returns true if application asked that this notification be part of a group.
205      */
isAppGroup()206     public boolean isAppGroup() {
207         if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
208             return true;
209         }
210         return false;
211     }
212 
writeToParcel(Parcel out, int flags)213     public void writeToParcel(Parcel out, int flags) {
214         out.writeString(this.pkg);
215         out.writeString(this.opPkg);
216         out.writeInt(this.id);
217         if (this.tag != null) {
218             out.writeInt(1);
219             out.writeString(this.tag);
220         } else {
221             out.writeInt(0);
222         }
223         out.writeInt(this.uid);
224         out.writeInt(this.initialPid);
225         this.notification.writeToParcel(out, flags);
226         user.writeToParcel(out, flags);
227         out.writeLong(this.postTime);
228         if (this.overrideGroupKey != null) {
229             out.writeInt(1);
230             out.writeString(this.overrideGroupKey);
231         } else {
232             out.writeInt(0);
233         }
234         if (this.mInstanceId != null) {
235             out.writeInt(1);
236             mInstanceId.writeToParcel(out, flags);
237         } else {
238             out.writeInt(0);
239         }
240     }
241 
describeContents()242     public int describeContents() {
243         return 0;
244     }
245 
246     public static final @android.annotation.NonNull
247             Parcelable.Creator<StatusBarNotification> CREATOR =
248             new Parcelable.Creator<StatusBarNotification>() {
249                 public StatusBarNotification createFromParcel(Parcel parcel) {
250                     return new StatusBarNotification(parcel);
251                 }
252 
253             public StatusBarNotification[] newArray(int size) {
254                 return new StatusBarNotification[size];
255             }
256     };
257 
258     /**
259      * @hide
260      */
cloneLight()261     public StatusBarNotification cloneLight() {
262         final Notification no = new Notification();
263         this.notification.cloneInto(no, false); // light copy
264         return cloneShallow(no);
265     }
266 
267     @Override
clone()268     public StatusBarNotification clone() {
269         return cloneShallow(this.notification.clone());
270     }
271 
272     /**
273      * @param notification Some kind of clone of this.notification.
274      * @return A shallow copy of self, with notification in place of this.notification.
275      */
cloneShallow(Notification notification)276     StatusBarNotification cloneShallow(Notification notification) {
277         StatusBarNotification result = new StatusBarNotification(this.pkg, this.opPkg,
278                 this.id, this.tag, this.uid, this.initialPid,
279                 notification, this.user, this.overrideGroupKey, this.postTime);
280         result.setInstanceId(this.mInstanceId);
281         return result;
282     }
283 
284     @Override
toString()285     public String toString() {
286         return formatSimple(
287                 "StatusBarNotification(pkg=%s user=%s id=%d tag=%s key=%s: %s)",
288                 this.pkg, this.user, this.id, this.tag,
289                 this.key, this.notification);
290     }
291 
292     /**
293      * Convenience method to check the notification's flags for
294      * {@link Notification#FLAG_ONGOING_EVENT}.
295      */
isOngoing()296     public boolean isOngoing() {
297         return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
298     }
299 
300     /**
301      * Convenience method to check the notification's flags for
302      * either {@link Notification#FLAG_ONGOING_EVENT} or
303      * {@link Notification#FLAG_NO_CLEAR}.
304      */
isClearable()305     public boolean isClearable() {
306         return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
307                 && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
308     }
309 
310     /**
311      * Returns a userid for whom this notification is intended.
312      *
313      * @deprecated Use {@link #getUser()} instead.
314      */
315     @Deprecated
getUserId()316     public int getUserId() {
317         return this.user.getIdentifier();
318     }
319 
320     /**
321      * Like {@link #getUserId()} but handles special users.
322      * @hide
323      */
getNormalizedUserId()324     public int getNormalizedUserId() {
325         int userId = getUserId();
326         if (userId == UserHandle.USER_ALL) {
327             userId = UserHandle.USER_SYSTEM;
328         }
329         return userId;
330     }
331 
332     /** The package that the notification belongs to. */
getPackageName()333     public String getPackageName() {
334         return pkg;
335     }
336 
337     /** The id supplied to {@link android.app.NotificationManager#notify(int, Notification)}. */
getId()338     public int getId() {
339         return id;
340     }
341 
342     /**
343      * The tag supplied to {@link android.app.NotificationManager#notify(int, Notification)},
344      * or null if no tag was specified.
345      */
getTag()346     public String getTag() {
347         return tag;
348     }
349 
350     /**
351      * The notifying app's ({@link #getPackageName()}'s) uid.
352      */
getUid()353     public int getUid() {
354         return uid;
355     }
356 
357     /**
358      * The package that posted the notification.
359      * <p> Might be different from {@link #getPackageName()} if the app owning the notification has
360      * a {@link NotificationManager#setNotificationDelegate(String) notification delegate}.
361      */
getOpPkg()362     public @NonNull String getOpPkg() {
363         return opPkg;
364     }
365 
366     /** @hide */
367     @UnsupportedAppUsage
getInitialPid()368     public int getInitialPid() {
369         return initialPid;
370     }
371 
372     /**
373      * The {@link android.app.Notification} supplied to
374      * {@link android.app.NotificationManager#notify(int, Notification)}.
375      */
getNotification()376     public Notification getNotification() {
377         return notification;
378     }
379 
380     /**
381      * The {@link android.os.UserHandle} for whom this notification is intended.
382      */
getUser()383     public UserHandle getUser() {
384         return user;
385     }
386 
387     /**
388      * The time (in {@link System#currentTimeMillis} time) the notification was posted,
389      * which may be different than {@link android.app.Notification#when}.
390      */
getPostTime()391     public long getPostTime() {
392         return postTime;
393     }
394 
395     /**
396      * A unique instance key for this notification record.
397      */
getKey()398     public String getKey() {
399         return key;
400     }
401 
402     /**
403      * A key that indicates the group with which this message ranks.
404      */
getGroupKey()405     public String getGroupKey() {
406         return groupKey;
407     }
408 
409     /**
410      * The ID passed to setGroup(), or the override, or null.
411      *
412      * @hide
413      */
getGroup()414     public String getGroup() {
415         if (overrideGroupKey != null) {
416             return overrideGroupKey;
417         }
418         return getNotification().getGroup();
419     }
420 
421     /**
422      * Sets the override group key.
423      */
setOverrideGroupKey(String overrideGroupKey)424     public void setOverrideGroupKey(String overrideGroupKey) {
425         this.overrideGroupKey = overrideGroupKey;
426         groupKey = groupKey();
427     }
428 
429     /**
430      * Returns the override group key.
431      */
getOverrideGroupKey()432     public String getOverrideGroupKey() {
433         return overrideGroupKey;
434     }
435 
436     /**
437      * @hide
438      */
clearPackageContext()439     public void clearPackageContext() {
440         mContext = null;
441     }
442 
443     /**
444      * @hide
445      */
getInstanceId()446     public InstanceId getInstanceId() {
447         return mInstanceId;
448     }
449 
450     /**
451      * @hide
452      */
setInstanceId(InstanceId instanceId)453     public void setInstanceId(InstanceId instanceId) {
454         mInstanceId = instanceId;
455     }
456 
457     /**
458      * @hide
459      */
460     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getPackageContext(Context context)461     public Context getPackageContext(Context context) {
462         if (mContext == null) {
463             try {
464                 ApplicationInfo ai = context.getPackageManager()
465                         .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
466                                 getNormalizedUserId());
467                 mContext = context.createApplicationContext(ai,
468                         Context.CONTEXT_RESTRICTED);
469             } catch (PackageManager.NameNotFoundException e) {
470                 mContext = null;
471             }
472         }
473         if (mContext == null) {
474             mContext = context;
475         }
476         return mContext;
477     }
478 
479     /**
480      * Returns a LogMaker that contains all basic information of the notification.
481      *
482      * @hide
483      */
getLogMaker()484     public LogMaker getLogMaker() {
485         LogMaker logMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN).setPackageName(getPackageName())
486                 .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId())
487                 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag())
488                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag())
489                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag())
490                 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY,
491                         getNotification().isGroupSummary() ? 1 : 0)
492                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CATEGORY,
493                         getNotification().category);
494         if (getNotification().extras != null) {
495             // Log the style used, if present.  We only log the hash here, as notification log
496             // events are frequent, while there are few styles (hence low chance of collisions).
497             String template = getNotification().extras.getString(Notification.EXTRA_TEMPLATE);
498             if (template != null && !template.isEmpty()) {
499                 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE,
500                         template.hashCode());
501             }
502             ArrayList<Person> people = getNotification().extras.getParcelableArrayList(
503                     Notification.EXTRA_PEOPLE_LIST);
504             if (people != null && !people.isEmpty()) {
505                 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_PEOPLE, people.size());
506             }
507         }
508         return logMaker;
509     }
510 
511     /**
512      * @hide
513      */
getShortcutId()514     public String getShortcutId() {
515         return getNotification().getShortcutId();
516     }
517 
518     /**
519      *  Returns a probably-unique string based on the notification's group name,
520      *  with no more than MAX_LOG_TAG_LENGTH characters.
521      * @return String based on group name of notification.
522      * @hide
523      */
getGroupLogTag()524     public String getGroupLogTag() {
525         return shortenTag(getGroup());
526     }
527 
528     /**
529      *  Returns a probably-unique string based on the notification's channel ID,
530      *  with no more than MAX_LOG_TAG_LENGTH characters.
531      * @return String based on channel ID of notification.
532      * @hide
533      */
getChannelIdLogTag()534     public String getChannelIdLogTag() {
535         if (notification.getChannelId() == null) {
536             return null;
537         }
538         return shortenTag(notification.getChannelId());
539     }
540 
541     // Make logTag with max size MAX_LOG_TAG_LENGTH.
542     // For shorter or equal tags, returns the tag.
543     // For longer tags, truncate the tag and append a hash of the full tag to
544     // fill the maximum size.
shortenTag(String logTag)545     private String shortenTag(String logTag) {
546         if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) {
547             return logTag;
548         }
549         String hash = Integer.toHexString(logTag.hashCode());
550         return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-"
551                 + hash;
552     }
553 }
554