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