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