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