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