1 /* 2 * Copyright (C) 2020 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 com.android.server.notification; 18 19 import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL; 20 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 21 import static android.service.notification.NotificationListenerService.REASON_CLEAR_DATA; 22 import static android.service.notification.NotificationListenerService.REASON_CLICK; 23 24 import android.annotation.DurationMillisLong; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.Notification; 28 import android.app.NotificationChannel; 29 import android.app.Person; 30 import android.os.Bundle; 31 import android.service.notification.NotificationListenerService; 32 import android.service.notification.NotificationStats; 33 import android.util.Log; 34 35 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; 36 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; 37 import com.android.internal.logging.InstanceId; 38 import com.android.internal.logging.UiEvent; 39 import com.android.internal.logging.UiEventLogger; 40 import com.android.internal.util.FrameworkStatsLog; 41 42 import java.time.Duration; 43 import java.util.ArrayList; 44 import java.util.Objects; 45 46 /** 47 * Interface for writing NotificationReported atoms to statsd log. Use NotificationRecordLoggerImpl 48 * in production. Use NotificationRecordLoggerFake for testing. 49 * @hide 50 */ 51 interface NotificationRecordLogger { 52 53 static final String TAG = "NotificationRecordLogger"; 54 55 // The high-level interface used by clients. 56 57 /** 58 * Prepare to log an atom reflecting the posting or update of a notification. 59 * 60 * The returned {@link NotificationReported} object, if any, should be supplied to 61 * {@link #logNotificationPosted}. Because only some updates are considered "interesting 62 * enough" to log, this method may return {@code null}. In that case, the follow-up call 63 * should not be performed. 64 * 65 * @param r The new {@link NotificationRecord}. 66 * @param old The previous {@link NotificationRecord}. Null if there was no previous record. 67 * @param position The position at which this notification is ranked. 68 * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user. 69 * @param groupId The {@link InstanceId} of the group summary notification, or null. 70 */ 71 @Nullable prepareToLogNotificationPosted(@ullable NotificationRecord r, @Nullable NotificationRecord old, int position, int buzzBeepBlink, InstanceId groupId)72 default NotificationReported prepareToLogNotificationPosted(@Nullable NotificationRecord r, 73 @Nullable NotificationRecord old, 74 int position, int buzzBeepBlink, 75 InstanceId groupId) { 76 NotificationRecordPair p = new NotificationRecordPair(r, old); 77 if (!p.shouldLogReported(buzzBeepBlink)) { 78 return null; 79 } 80 return new NotificationReported(p, NotificationReportedEvent.fromRecordPair(p), position, 81 buzzBeepBlink, groupId); 82 } 83 84 /** 85 * Log a NotificationReported atom reflecting the posting or update of a notification. 86 */ logNotificationPosted(NotificationReported nr)87 void logNotificationPosted(NotificationReported nr); 88 89 /** 90 * Logs a NotificationReported atom reflecting an adjustment to a notification. 91 * Unlike for posted notifications, this method is guaranteed to log a notification update, 92 * so the caller must take responsibility for checking that that logging update is necessary, 93 * and that the notification is meaningfully changed. 94 * @param r The NotificationRecord. If null, no action is taken. 95 * @param position The position at which this notification is ranked. 96 * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user. 97 * @param groupId The instance Id of the group summary notification, or null. 98 */ logNotificationAdjusted(@ullable NotificationRecord r, int position, int buzzBeepBlink, InstanceId groupId)99 void logNotificationAdjusted(@Nullable NotificationRecord r, 100 int position, int buzzBeepBlink, 101 InstanceId groupId); 102 103 /** 104 * Logs a notification cancel / dismiss event using UiEventReported (event ids from the 105 * NotificationCancelledEvents enum). 106 * @param r The NotificationRecord. If null, no action is taken. 107 * @param reason The reason the notification was canceled. 108 * @param dismissalSurface The surface the notification was dismissed from. 109 */ logNotificationCancelled(@ullable NotificationRecord r, @NotificationListenerService.NotificationCancelReason int reason, @NotificationStats.DismissalSurface int dismissalSurface)110 default void logNotificationCancelled(@Nullable NotificationRecord r, 111 @NotificationListenerService.NotificationCancelReason int reason, 112 @NotificationStats.DismissalSurface int dismissalSurface) { 113 log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r); 114 } 115 116 /** 117 * Logs a notification visibility change event using UiEventReported (event ids from the 118 * NotificationEvents enum). 119 * @param r The NotificationRecord. If null, no action is taken. 120 * @param visible True if the notification became visible. 121 */ logNotificationVisibility(@ullable NotificationRecord r, boolean visible)122 default void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible) { 123 log(NotificationEvent.fromVisibility(visible), r); 124 } 125 126 // The UiEventReported logging methods are implemented in terms of this lower-level interface. 127 128 /** Logs a UiEventReported event for the given notification. */ log(UiEventLogger.UiEventEnum event, NotificationRecord r)129 void log(UiEventLogger.UiEventEnum event, NotificationRecord r); 130 131 /** Logs a UiEventReported event that is not associated with any notification. */ log(UiEventLogger.UiEventEnum event)132 void log(UiEventLogger.UiEventEnum event); 133 134 /** 135 * The UiEvent enums that this class can log. 136 */ 137 enum NotificationReportedEvent implements UiEventLogger.UiEventEnum { 138 @UiEvent(doc = "New notification enqueued to post") 139 NOTIFICATION_POSTED(162), 140 @UiEvent(doc = "Notification substantially updated, or alerted again.") 141 NOTIFICATION_UPDATED(163), 142 @UiEvent(doc = "Notification adjusted by assistant.") 143 NOTIFICATION_ADJUSTED(908); 144 145 private final int mId; NotificationReportedEvent(int id)146 NotificationReportedEvent(int id) { 147 mId = id; 148 } getId()149 @Override public int getId() { 150 return mId; 151 } 152 fromRecordPair(NotificationRecordPair p)153 public static NotificationReportedEvent fromRecordPair(NotificationRecordPair p) { 154 return (p.old != null) ? NotificationReportedEvent.NOTIFICATION_UPDATED : 155 NotificationReportedEvent.NOTIFICATION_POSTED; 156 } 157 } 158 159 enum NotificationCancelledEvent implements UiEventLogger.UiEventEnum { 160 INVALID(0), 161 @UiEvent(doc = "Notification was canceled due to a notification click.") 162 NOTIFICATION_CANCEL_CLICK(164), 163 @UiEvent(doc = "Notification was canceled due to a user dismissal, surface not specified.") 164 NOTIFICATION_CANCEL_USER_OTHER(165), 165 @UiEvent(doc = "Notification was canceled due to a user dismiss-all (from the notification" 166 + " shade).") 167 NOTIFICATION_CANCEL_USER_CANCEL_ALL(166), 168 @UiEvent(doc = "Notification was canceled due to an inflation error.") 169 NOTIFICATION_CANCEL_ERROR(167), 170 @UiEvent(doc = "Notification was canceled by the package manager modifying the package.") 171 NOTIFICATION_CANCEL_PACKAGE_CHANGED(168), 172 @UiEvent(doc = "Notification was canceled by the owning user context being stopped.") 173 NOTIFICATION_CANCEL_USER_STOPPED(169), 174 @UiEvent(doc = "Notification was canceled by the user banning the package.") 175 NOTIFICATION_CANCEL_PACKAGE_BANNED(170), 176 @UiEvent(doc = "Notification was canceled by the app canceling this specific notification.") 177 NOTIFICATION_CANCEL_APP_CANCEL(171), 178 @UiEvent(doc = "Notification was canceled by the app cancelling all its notifications.") 179 NOTIFICATION_CANCEL_APP_CANCEL_ALL(172), 180 @UiEvent(doc = "Notification was canceled by a listener reporting a user dismissal.") 181 NOTIFICATION_CANCEL_LISTENER_CANCEL(173), 182 @UiEvent(doc = "Notification was canceled by a listener reporting a user dismiss all.") 183 NOTIFICATION_CANCEL_LISTENER_CANCEL_ALL(174), 184 @UiEvent(doc = "Notification was canceled because it was a member of a canceled group.") 185 NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED(175), 186 @UiEvent(doc = "Notification was canceled because it was an invisible member of a group.") 187 NOTIFICATION_CANCEL_GROUP_OPTIMIZATION(176), 188 @UiEvent(doc = "Notification was canceled by the device administrator suspending the " 189 + "package.") 190 NOTIFICATION_CANCEL_PACKAGE_SUSPENDED(177), 191 @UiEvent(doc = "Notification was canceled by the owning managed profile being turned off.") 192 NOTIFICATION_CANCEL_PROFILE_TURNED_OFF(178), 193 @UiEvent(doc = "Autobundled summary notification was canceled because its group was " 194 + "unbundled") 195 NOTIFICATION_CANCEL_UNAUTOBUNDLED(179), 196 @UiEvent(doc = "Notification was canceled by the user banning the channel.") 197 NOTIFICATION_CANCEL_CHANNEL_BANNED(180), 198 @UiEvent(doc = "Notification was snoozed.") 199 NOTIFICATION_CANCEL_SNOOZED(181), 200 @UiEvent(doc = "Notification was canceled due to timeout") 201 NOTIFICATION_CANCEL_TIMEOUT(182), 202 @UiEvent(doc = "Notification was canceled due to the backing channel being deleted") 203 NOTIFICATION_CANCEL_CHANNEL_REMOVED(1261), 204 @UiEvent(doc = "Notification was canceled due to the app's storage being cleared") 205 NOTIFICATION_CANCEL_CLEAR_DATA(1262), 206 // Values above this line must remain in the same order as the corresponding 207 // NotificationCancelReason enum values. 208 @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.") 209 NOTIFICATION_CANCEL_USER_PEEK(190), 210 @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display") 211 NOTIFICATION_CANCEL_USER_AOD(191), 212 @UiEvent(doc = "Notification was canceled due to user dismissal from a bubble") 213 NOTIFICATION_CANCEL_USER_BUBBLE(1228), 214 @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen") 215 NOTIFICATION_CANCEL_USER_LOCKSCREEN(193), 216 @UiEvent(doc = "Notification was canceled due to user dismissal from the notification" 217 + " shade.") 218 NOTIFICATION_CANCEL_USER_SHADE(192), 219 @UiEvent(doc = "Notification was canceled due to an assistant adjustment update.") 220 NOTIFICATION_CANCEL_ASSISTANT(906); 221 222 private final int mId; NotificationCancelledEvent(int id)223 NotificationCancelledEvent(int id) { 224 mId = id; 225 } getId()226 @Override public int getId() { 227 return mId; 228 } 229 fromCancelReason( @otificationListenerService.NotificationCancelReason int reason, @NotificationStats.DismissalSurface int surface)230 public static NotificationCancelledEvent fromCancelReason( 231 @NotificationListenerService.NotificationCancelReason int reason, 232 @NotificationStats.DismissalSurface int surface) { 233 // Shouldn't be possible to get a non-dismissed notification here. 234 if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) { 235 Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason); 236 return INVALID; 237 } 238 239 // User cancels have a meaningful surface, which we differentiate by. See b/149038335 240 // for caveats. 241 if (reason == REASON_CANCEL) { 242 switch (surface) { 243 case NotificationStats.DISMISSAL_PEEK: 244 return NOTIFICATION_CANCEL_USER_PEEK; 245 case NotificationStats.DISMISSAL_AOD: 246 return NOTIFICATION_CANCEL_USER_AOD; 247 case NotificationStats.DISMISSAL_SHADE: 248 return NOTIFICATION_CANCEL_USER_SHADE; 249 case NotificationStats.DISMISSAL_BUBBLE: 250 return NOTIFICATION_CANCEL_USER_BUBBLE; 251 case NotificationStats.DISMISSAL_LOCKSCREEN: 252 return NOTIFICATION_CANCEL_USER_LOCKSCREEN; 253 case NotificationStats.DISMISSAL_OTHER: 254 return NOTIFICATION_CANCEL_USER_OTHER; 255 default: 256 Log.wtf(TAG, "Unexpected surface: " + surface + " with reason " + reason); 257 return INVALID; 258 } 259 } else { 260 if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) { 261 return NotificationCancelledEvent.values()[reason]; 262 } 263 if (reason == REASON_ASSISTANT_CANCEL) { 264 return NotificationCancelledEvent.NOTIFICATION_CANCEL_ASSISTANT; 265 } 266 Log.wtf(TAG, "Unexpected reason: " + reason + " with surface " + surface); 267 return INVALID; 268 } 269 } 270 } 271 272 enum NotificationEvent implements UiEventLogger.UiEventEnum { 273 @UiEvent(doc = "Notification became visible.") 274 NOTIFICATION_OPEN(197), 275 @UiEvent(doc = "Notification stopped being visible.") 276 NOTIFICATION_CLOSE(198), 277 @UiEvent(doc = "Notification was snoozed.") 278 NOTIFICATION_SNOOZED(317), 279 @UiEvent(doc = "Notification was not posted because its app is snoozed.") 280 NOTIFICATION_NOT_POSTED_SNOOZED(319), 281 @UiEvent(doc = "Notification was clicked.") 282 NOTIFICATION_CLICKED(320), 283 @UiEvent(doc = "Notification action was clicked; unexpected position.") 284 NOTIFICATION_ACTION_CLICKED(321), 285 @UiEvent(doc = "Notification detail was expanded due to non-user action.") 286 NOTIFICATION_DETAIL_OPEN_SYSTEM(327), 287 @UiEvent(doc = "Notification detail was collapsed due to non-user action.") 288 NOTIFICATION_DETAIL_CLOSE_SYSTEM(328), 289 @UiEvent(doc = "Notification detail was expanded due to user action.") 290 NOTIFICATION_DETAIL_OPEN_USER(329), 291 @UiEvent(doc = "Notification detail was collapsed due to user action.") 292 NOTIFICATION_DETAIL_CLOSE_USER(330), 293 @UiEvent(doc = "Notification direct reply action was used.") 294 NOTIFICATION_DIRECT_REPLIED(331), 295 @UiEvent(doc = "Notification smart reply action was used.") 296 NOTIFICATION_SMART_REPLIED(332), 297 @UiEvent(doc = "Notification smart reply action was visible.") 298 NOTIFICATION_SMART_REPLY_VISIBLE(333), 299 @UiEvent(doc = "App-generated notification action at position 0 was clicked.") 300 NOTIFICATION_ACTION_CLICKED_0(450), 301 @UiEvent(doc = "App-generated notification action at position 1 was clicked.") 302 NOTIFICATION_ACTION_CLICKED_1(451), 303 @UiEvent(doc = "App-generated notification action at position 2 was clicked.") 304 NOTIFICATION_ACTION_CLICKED_2(452), 305 @UiEvent(doc = "Contextual notification action at position 0 was clicked.") 306 NOTIFICATION_CONTEXTUAL_ACTION_CLICKED_0(453), 307 @UiEvent(doc = "Contextual notification action at position 1 was clicked.") 308 NOTIFICATION_CONTEXTUAL_ACTION_CLICKED_1(454), 309 @UiEvent(doc = "Contextual notification action at position 2 was clicked.") 310 NOTIFICATION_CONTEXTUAL_ACTION_CLICKED_2(455), 311 @UiEvent(doc = "Notification assistant generated notification action at 0 was clicked.") 312 NOTIFICATION_ASSIST_ACTION_CLICKED_0(456), 313 @UiEvent(doc = "Notification assistant generated notification action at 1 was clicked.") 314 NOTIFICATION_ASSIST_ACTION_CLICKED_1(457), 315 @UiEvent(doc = "Notification assistant generated notification action at 2 was clicked.") 316 NOTIFICATION_ASSIST_ACTION_CLICKED_2(458); 317 318 private final int mId; NotificationEvent(int id)319 NotificationEvent(int id) { 320 mId = id; 321 } getId()322 @Override public int getId() { 323 return mId; 324 } 325 fromVisibility(boolean visible)326 public static NotificationEvent fromVisibility(boolean visible) { 327 return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE; 328 } fromExpanded(boolean expanded, boolean userAction)329 public static NotificationEvent fromExpanded(boolean expanded, boolean userAction) { 330 if (userAction) { 331 return expanded ? NOTIFICATION_DETAIL_OPEN_USER : NOTIFICATION_DETAIL_CLOSE_USER; 332 } 333 return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM; 334 } fromAction(int index, boolean isAssistant, boolean isContextual)335 public static NotificationEvent fromAction(int index, boolean isAssistant, 336 boolean isContextual) { 337 if (index < 0 || index > 2) { 338 return NOTIFICATION_ACTION_CLICKED; 339 } 340 if (isAssistant) { // Assistant actions are contextual by definition 341 return NotificationEvent.values()[ 342 NOTIFICATION_ASSIST_ACTION_CLICKED_0.ordinal() + index]; 343 } 344 if (isContextual) { 345 return NotificationEvent.values()[ 346 NOTIFICATION_CONTEXTUAL_ACTION_CLICKED_0.ordinal() + index]; 347 } 348 return NotificationEvent.values()[NOTIFICATION_ACTION_CLICKED_0.ordinal() + index]; 349 } 350 } 351 352 enum NotificationPanelEvent implements UiEventLogger.UiEventEnum { 353 @UiEvent(doc = "Notification panel became visible.") 354 NOTIFICATION_PANEL_OPEN(325), 355 @UiEvent(doc = "Notification panel stopped being visible.") 356 NOTIFICATION_PANEL_CLOSE(326); 357 358 private final int mId; NotificationPanelEvent(int id)359 NotificationPanelEvent(int id) { 360 mId = id; 361 } getId()362 @Override public int getId() { 363 return mId; 364 } 365 } 366 367 /** 368 * A helper for extracting logging information from one or two NotificationRecords. 369 */ 370 class NotificationRecordPair { 371 public final NotificationRecord r, old; 372 /** 373 * Construct from one or two NotificationRecords. 374 * @param r The new NotificationRecord. If null, only shouldLog() method is usable. 375 * @param old The previous NotificationRecord. Null if there was no previous record. 376 */ NotificationRecordPair(@ullable NotificationRecord r, @Nullable NotificationRecord old)377 NotificationRecordPair(@Nullable NotificationRecord r, @Nullable NotificationRecord old) { 378 this.r = r; 379 this.old = old; 380 } 381 382 /** 383 * @return True if old is null, alerted, or important logged fields have changed. 384 */ shouldLogReported(int buzzBeepBlink)385 boolean shouldLogReported(int buzzBeepBlink) { 386 if (r == null) { 387 return false; 388 } 389 if ((old == null) || (buzzBeepBlink > 0)) { 390 return true; 391 } 392 393 return !(Objects.equals(r.getSbn().getChannelIdLogTag(), 394 old.getSbn().getChannelIdLogTag()) 395 && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag()) 396 && (r.getSbn().getNotification().isGroupSummary() 397 == old.getSbn().getNotification().isGroupSummary()) 398 && Objects.equals(r.getSbn().getNotification().category, 399 old.getSbn().getNotification().category) 400 && (r.getImportance() == old.getImportance()) 401 && (getLoggingImportance(r) == getLoggingImportance(old)) 402 && r.rankingScoreMatches(old.getRankingScore())); 403 } 404 405 /** 406 * @return hash code for the notification style class, or 0 if none exists. 407 */ getStyle()408 public int getStyle() { 409 return getStyle(r.getSbn().getNotification().extras); 410 } 411 getStyle(@ullable Bundle extras)412 private int getStyle(@Nullable Bundle extras) { 413 if (extras != null) { 414 String template = extras.getString(Notification.EXTRA_TEMPLATE); 415 if (template != null && !template.isEmpty()) { 416 return template.hashCode(); 417 } 418 } 419 return 0; 420 } 421 getNumPeople()422 int getNumPeople() { 423 return getNumPeople(r.getSbn().getNotification().extras); 424 } 425 getNumPeople(@ullable Bundle extras)426 private int getNumPeople(@Nullable Bundle extras) { 427 if (extras != null) { 428 ArrayList<Person> people = extras.getParcelableArrayList( 429 Notification.EXTRA_PEOPLE_LIST, android.app.Person.class); 430 if (people != null && !people.isEmpty()) { 431 return people.size(); 432 } 433 } 434 return 0; 435 } 436 getAssistantHash()437 int getAssistantHash() { 438 String assistant = r.getAdjustmentIssuer(); 439 return (assistant == null) ? 0 : assistant.hashCode(); 440 } 441 getInstanceId()442 int getInstanceId() { 443 return (r.getSbn().getInstanceId() == null ? 0 : r.getSbn().getInstanceId().getId()); 444 } 445 446 /** 447 * @return Small hash of the notification ID, and tag (if present). 448 */ getNotificationIdHash()449 int getNotificationIdHash() { 450 return SmallHash.hash(Objects.hashCode(r.getSbn().getTag()) ^ r.getSbn().getId()); 451 } 452 453 /** 454 * @return Small hash of the channel ID, if present, or 0 otherwise. 455 */ getChannelIdHash()456 int getChannelIdHash() { 457 return SmallHash.hash(r.getSbn().getNotification().getChannelId()); 458 } 459 460 /** 461 * @return Small hash of the group ID, respecting group override if present. 0 otherwise. 462 */ getGroupIdHash()463 int getGroupIdHash() { 464 return SmallHash.hash(r.getSbn().getGroup()); 465 } 466 467 } 468 469 /** Data object corresponding to a NotificationReported atom. 470 * 471 * Fields must be kept in sync with frameworks/proto_logging/stats/atoms.proto. 472 */ 473 class NotificationReported { 474 final int event_id; 475 final int uid; 476 final String package_name; 477 final int instance_id; 478 final int notification_id_hash; 479 final int channel_id_hash; 480 final int group_id_hash; 481 final int group_instance_id; 482 final boolean is_group_summary; 483 final String category; 484 final int style; 485 final int num_people; 486 final int position; 487 final int importance; 488 final int alerting; 489 final int importance_source; 490 final int importance_initial; 491 final int importance_initial_source; 492 final int importance_asst; 493 final int assistant_hash; 494 final float assistant_ranking_score; 495 final boolean is_ongoing; 496 final boolean is_foreground_service; 497 final long timeout_millis; 498 final boolean is_non_dismissible; 499 final int fsi_state; 500 final boolean is_locked; 501 final int age_in_minutes; 502 @DurationMillisLong long post_duration_millis; // Not final; calculated at the end. 503 NotificationReported(NotificationRecordPair p, NotificationReportedEvent eventType, int position, int buzzBeepBlink, InstanceId groupId)504 NotificationReported(NotificationRecordPair p, 505 NotificationReportedEvent eventType, int position, int buzzBeepBlink, 506 InstanceId groupId) { 507 this.event_id = eventType.getId(); 508 this.uid = p.r.getUid(); 509 this.package_name = p.r.getSbn().getPackageName(); 510 this.instance_id = p.getInstanceId(); 511 this.notification_id_hash = p.getNotificationIdHash(); 512 this.channel_id_hash = p.getChannelIdHash(); 513 this.group_id_hash = p.getGroupIdHash(); 514 this.group_instance_id = (groupId == null) ? 0 : groupId.getId(); 515 this.is_group_summary = p.r.getSbn().getNotification().isGroupSummary(); 516 this.category = p.r.getSbn().getNotification().category; 517 this.style = p.getStyle(); 518 this.num_people = p.getNumPeople(); 519 this.position = position; 520 this.importance = NotificationRecordLogger.getLoggingImportance(p.r); 521 this.alerting = buzzBeepBlink; 522 this.importance_source = p.r.getImportanceExplanationCode(); 523 this.importance_initial = p.r.getInitialImportance(); 524 this.importance_initial_source = p.r.getInitialImportanceExplanationCode(); 525 this.importance_asst = p.r.getAssistantImportance(); 526 this.assistant_hash = p.getAssistantHash(); 527 this.assistant_ranking_score = p.r.getRankingScore(); 528 this.is_ongoing = p.r.getSbn().isOngoing(); 529 this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r); 530 this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter(); 531 this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r); 532 533 final boolean hasFullScreenIntent = 534 p.r.getSbn().getNotification().fullScreenIntent != null; 535 536 final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags 537 & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0; 538 539 this.fsi_state = NotificationRecordLogger.getFsiState( 540 hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType); 541 542 this.is_locked = p.r.isLocked(); 543 544 this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes( 545 p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().getWhen()); 546 } 547 } 548 549 /** 550 * @param r NotificationRecord 551 * @return Logging importance of record, taking important conversation channels into account. 552 */ getLoggingImportance(@onNull NotificationRecord r)553 static int getLoggingImportance(@NonNull NotificationRecord r) { 554 final int importance = r.getImportance(); 555 final NotificationChannel channel = r.getChannel(); 556 if (channel == null) { 557 return importance; 558 } 559 return NotificationChannelLogger.getLoggingImportance(channel, importance); 560 } 561 562 /** 563 * @param r NotificationRecord 564 * @return Whether the notification is a foreground service notification. 565 */ isForegroundService(@onNull NotificationRecord r)566 static boolean isForegroundService(@NonNull NotificationRecord r) { 567 if (r.getSbn() == null || r.getSbn().getNotification() == null) { 568 return false; 569 } 570 return (r.getSbn().getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 571 } 572 573 /** 574 * @return Whether the notification is a non-dismissible notification. 575 */ isNonDismissible(@onNull NotificationRecord r)576 static boolean isNonDismissible(@NonNull NotificationRecord r) { 577 if (r.getSbn() == null || r.getSbn().getNotification() == null) { 578 return false; 579 } 580 return (r.getNotification().flags & Notification.FLAG_NO_DISMISS) != 0; 581 } 582 583 /** 584 * @return FrameworkStatsLog enum of the state of the full screen intent posted with this 585 * notification. 586 */ getFsiState(boolean hasFullScreenIntent, boolean hasFsiRequestedButDeniedFlag, NotificationReportedEvent eventType)587 static int getFsiState(boolean hasFullScreenIntent, 588 boolean hasFsiRequestedButDeniedFlag, 589 NotificationReportedEvent eventType) { 590 if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) { 591 // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth, 592 // so we should log 0 when possible. 593 return 0; 594 } 595 if (hasFullScreenIntent) { 596 return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED; 597 } 598 if (hasFsiRequestedButDeniedFlag) { 599 return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED; 600 } 601 return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI; 602 } 603 604 /** 605 * @param postTimeMs time (in {@link System#currentTimeMillis} time) the notification was posted 606 * @param whenMs A timestamp related to this notification, in milliseconds since the epoch. 607 * @return difference in duration as an integer in minutes 608 */ getAgeInMinutes(long postTimeMs, long whenMs)609 static int getAgeInMinutes(long postTimeMs, long whenMs) { 610 return (int) Duration.ofMillis(postTimeMs - whenMs).toMinutes(); 611 } 612 } 613