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.annotation.UnsupportedAppUsage; 21 import android.app.Notification; 22 import android.app.NotificationManager; 23 import android.app.Person; 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