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 * @hide 302 * 303 * Convenience method to check the notification's flags for 304 * {@link Notification#FLAG_NO_DISMISS}. 305 */ isNonDismissable()306 public boolean isNonDismissable() { 307 return (notification.flags & Notification.FLAG_NO_DISMISS) != 0; 308 } 309 310 /** 311 * Convenience method to check the notification's flags for 312 * either {@link Notification#FLAG_ONGOING_EVENT} or 313 * {@link Notification#FLAG_NO_CLEAR}. 314 */ isClearable()315 public boolean isClearable() { 316 return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0) 317 && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); 318 } 319 320 /** 321 * Returns a userid for whom this notification is intended. 322 * 323 * @deprecated Use {@link #getUser()} instead. 324 */ 325 @Deprecated getUserId()326 public int getUserId() { 327 return this.user.getIdentifier(); 328 } 329 330 /** 331 * Like {@link #getUserId()} but handles special users. 332 * @hide 333 */ getNormalizedUserId()334 public int getNormalizedUserId() { 335 int userId = getUserId(); 336 if (userId == UserHandle.USER_ALL) { 337 userId = UserHandle.USER_SYSTEM; 338 } 339 return userId; 340 } 341 342 /** The package that the notification belongs to. */ getPackageName()343 public String getPackageName() { 344 return pkg; 345 } 346 347 /** The id supplied to {@link android.app.NotificationManager#notify(int, Notification)}. */ getId()348 public int getId() { 349 return id; 350 } 351 352 /** 353 * The tag supplied to {@link android.app.NotificationManager#notify(int, Notification)}, 354 * or null if no tag was specified. 355 */ getTag()356 public String getTag() { 357 return tag; 358 } 359 360 /** 361 * The notifying app's ({@link #getPackageName()}'s) uid. 362 */ getUid()363 public int getUid() { 364 return uid; 365 } 366 367 /** 368 * The package that posted the notification. 369 * <p> Might be different from {@link #getPackageName()} if the app owning the notification has 370 * a {@link NotificationManager#setNotificationDelegate(String) notification delegate}. 371 */ getOpPkg()372 public @NonNull String getOpPkg() { 373 return opPkg; 374 } 375 376 /** @hide */ 377 @UnsupportedAppUsage getInitialPid()378 public int getInitialPid() { 379 return initialPid; 380 } 381 382 /** 383 * The {@link android.app.Notification} supplied to 384 * {@link android.app.NotificationManager#notify(int, Notification)}. 385 */ getNotification()386 public Notification getNotification() { 387 return notification; 388 } 389 390 /** 391 * The {@link android.os.UserHandle} for whom this notification is intended. 392 */ getUser()393 public UserHandle getUser() { 394 return user; 395 } 396 397 /** 398 * The time (in {@link System#currentTimeMillis} time) the notification was posted, 399 * which may be different than {@link android.app.Notification#when}. 400 */ getPostTime()401 public long getPostTime() { 402 return postTime; 403 } 404 405 /** 406 * A unique instance key for this notification record. 407 */ getKey()408 public String getKey() { 409 return key; 410 } 411 412 /** 413 * A key that indicates the group with which this message ranks. 414 */ getGroupKey()415 public String getGroupKey() { 416 return groupKey; 417 } 418 419 /** 420 * The ID passed to setGroup(), or the override, or null. 421 * 422 * @hide 423 */ getGroup()424 public String getGroup() { 425 if (overrideGroupKey != null) { 426 return overrideGroupKey; 427 } 428 return getNotification().getGroup(); 429 } 430 431 /** 432 * Sets the override group key. 433 */ setOverrideGroupKey(String overrideGroupKey)434 public void setOverrideGroupKey(String overrideGroupKey) { 435 this.overrideGroupKey = overrideGroupKey; 436 groupKey = groupKey(); 437 } 438 439 /** 440 * Returns the override group key. 441 */ getOverrideGroupKey()442 public String getOverrideGroupKey() { 443 return overrideGroupKey; 444 } 445 446 /** 447 * @hide 448 */ clearPackageContext()449 public void clearPackageContext() { 450 mContext = null; 451 } 452 453 /** 454 * @hide 455 */ getInstanceId()456 public InstanceId getInstanceId() { 457 return mInstanceId; 458 } 459 460 /** 461 * @hide 462 */ setInstanceId(InstanceId instanceId)463 public void setInstanceId(InstanceId instanceId) { 464 mInstanceId = instanceId; 465 } 466 467 /** 468 * @hide 469 */ 470 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getPackageContext(Context context)471 public Context getPackageContext(Context context) { 472 if (mContext == null) { 473 try { 474 ApplicationInfo ai = context.getPackageManager() 475 .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES, 476 getNormalizedUserId()); 477 mContext = context.createApplicationContext(ai, 478 Context.CONTEXT_RESTRICTED); 479 } catch (PackageManager.NameNotFoundException e) { 480 mContext = null; 481 } 482 } 483 if (mContext == null) { 484 mContext = context; 485 } 486 return mContext; 487 } 488 489 /** 490 * Returns a LogMaker that contains all basic information of the notification. 491 * 492 * @hide 493 */ getLogMaker()494 public LogMaker getLogMaker() { 495 LogMaker logMaker = new LogMaker(MetricsEvent.VIEW_UNKNOWN).setPackageName(getPackageName()) 496 .addTaggedData(MetricsEvent.NOTIFICATION_ID, getId()) 497 .addTaggedData(MetricsEvent.NOTIFICATION_TAG, getTag()) 498 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, getChannelIdLogTag()) 499 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID, getGroupLogTag()) 500 .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_SUMMARY, 501 getNotification().isGroupSummary() ? 1 : 0) 502 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CATEGORY, 503 getNotification().category); 504 if (getNotification().extras != null) { 505 // Log the style used, if present. We only log the hash here, as notification log 506 // events are frequent, while there are few styles (hence low chance of collisions). 507 String template = getNotification().extras.getString(Notification.EXTRA_TEMPLATE); 508 if (template != null && !template.isEmpty()) { 509 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE, 510 template.hashCode()); 511 } 512 ArrayList<Person> people = getNotification().extras.getParcelableArrayList( 513 Notification.EXTRA_PEOPLE_LIST, android.app.Person.class); 514 if (people != null && !people.isEmpty()) { 515 logMaker.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_PEOPLE, people.size()); 516 } 517 } 518 return logMaker; 519 } 520 521 /** 522 * @hide 523 */ getShortcutId()524 public String getShortcutId() { 525 return getNotification().getShortcutId(); 526 } 527 528 /** 529 * Returns a probably-unique string based on the notification's group name, 530 * with no more than MAX_LOG_TAG_LENGTH characters. 531 * @return String based on group name of notification. 532 * @hide 533 */ getGroupLogTag()534 public String getGroupLogTag() { 535 return shortenTag(getGroup()); 536 } 537 538 /** 539 * Returns a probably-unique string based on the notification's channel ID, 540 * with no more than MAX_LOG_TAG_LENGTH characters. 541 * @return String based on channel ID of notification. 542 * @hide 543 */ getChannelIdLogTag()544 public String getChannelIdLogTag() { 545 if (notification.getChannelId() == null) { 546 return null; 547 } 548 return shortenTag(notification.getChannelId()); 549 } 550 551 // Make logTag with max size MAX_LOG_TAG_LENGTH. 552 // For shorter or equal tags, returns the tag. 553 // For longer tags, truncate the tag and append a hash of the full tag to 554 // fill the maximum size. shortenTag(String logTag)555 private String shortenTag(String logTag) { 556 if (logTag == null || logTag.length() <= MAX_LOG_TAG_LENGTH) { 557 return logTag; 558 } 559 String hash = Integer.toHexString(logTag.hashCode()); 560 return logTag.substring(0, MAX_LOG_TAG_LENGTH - hash.length() - 1) + "-" 561 + hash; 562 } 563 } 564