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