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