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