blob: eba57304124a5a3e44ca155c8fd88bff0e4c7afb [file] [log] [blame]
Will Brockman2b6959e2020-01-22 09:59:50 -05001/*
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
17package com.android.server.notification;
18
Will Brockman75c60572020-01-31 10:30:27 -050019import static android.service.notification.NotificationListenerService.REASON_CANCEL;
20import static android.service.notification.NotificationListenerService.REASON_CLICK;
21import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
22
Will Brockman2b6959e2020-01-22 09:59:50 -050023import android.annotation.Nullable;
24import android.app.Notification;
25import android.app.Person;
26import android.os.Bundle;
Will Brockman75c60572020-01-31 10:30:27 -050027import android.service.notification.NotificationListenerService;
28import android.service.notification.NotificationStats;
Will Brockman2b6959e2020-01-22 09:59:50 -050029
30import com.android.internal.logging.UiEvent;
31import com.android.internal.logging.UiEventLogger;
32
33import java.util.ArrayList;
34import java.util.Objects;
35
36/**
Will Brockman9918db92020-03-06 16:48:39 -050037 * Interface for writing NotificationReported atoms to statsd log. Use NotificationRecordLoggerImpl
38 * in production. Use NotificationRecordLoggerFake for testing.
Will Brockman2b6959e2020-01-22 09:59:50 -050039 * @hide
40 */
41public interface NotificationRecordLogger {
42
Will Brockman9918db92020-03-06 16:48:39 -050043 // The high-level interface used by clients.
44
Will Brockman2b6959e2020-01-22 09:59:50 -050045 /**
Will Brockmand3d49332020-02-10 19:43:03 -050046 * May log a NotificationReported atom reflecting the posting or update of a notification.
Will Brockman2b6959e2020-01-22 09:59:50 -050047 * @param r The new NotificationRecord. If null, no action is taken.
48 * @param old The previous NotificationRecord. Null if there was no previous record.
49 * @param position The position at which this notification is ranked.
50 * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
51 */
Will Brockmand3d49332020-02-10 19:43:03 -050052 void maybeLogNotificationPosted(@Nullable NotificationRecord r,
53 @Nullable NotificationRecord old,
Will Brockman2b6959e2020-01-22 09:59:50 -050054 int position, int buzzBeepBlink);
55
56 /**
Will Brockman75c60572020-01-31 10:30:27 -050057 * Logs a notification cancel / dismiss event using UiEventReported (event ids from the
58 * NotificationCancelledEvents enum).
59 * @param r The NotificationRecord. If null, no action is taken.
60 * @param reason The reason the notification was canceled.
61 * @param dismissalSurface The surface the notification was dismissed from.
62 */
Will Brockman9918db92020-03-06 16:48:39 -050063 default void logNotificationCancelled(@Nullable NotificationRecord r,
Will Brockman75c60572020-01-31 10:30:27 -050064 @NotificationListenerService.NotificationCancelReason int reason,
Will Brockman9918db92020-03-06 16:48:39 -050065 @NotificationStats.DismissalSurface int dismissalSurface) {
66 log(NotificationCancelledEvent.fromCancelReason(reason, dismissalSurface), r);
67 }
Will Brockman75c60572020-01-31 10:30:27 -050068
69 /**
Will Brockmand3d49332020-02-10 19:43:03 -050070 * Logs a notification visibility change event using UiEventReported (event ids from the
71 * NotificationEvents enum).
72 * @param r The NotificationRecord. If null, no action is taken.
73 * @param visible True if the notification became visible.
74 */
Will Brockman9918db92020-03-06 16:48:39 -050075 default void logNotificationVisibility(@Nullable NotificationRecord r, boolean visible) {
76 log(NotificationEvent.fromVisibility(visible), r);
77 }
78
79 // The UiEventReported logging methods are implemented in terms of this lower-level interface.
80
81 /** Logs a UiEventReported event for the given notification. */
82 void log(UiEventLogger.UiEventEnum event, NotificationRecord r);
83
84 /** Logs a UiEventReported event that is not associated with any notification. */
85 void log(UiEventLogger.UiEventEnum event);
Will Brockmand3d49332020-02-10 19:43:03 -050086
87 /**
Will Brockman2b6959e2020-01-22 09:59:50 -050088 * The UiEvent enums that this class can log.
89 */
Will Brockman75c60572020-01-31 10:30:27 -050090 enum NotificationReportedEvent implements UiEventLogger.UiEventEnum {
Will Brockman2b6959e2020-01-22 09:59:50 -050091 @UiEvent(doc = "New notification enqueued to post")
92 NOTIFICATION_POSTED(162),
Will Brockman75c60572020-01-31 10:30:27 -050093 @UiEvent(doc = "Notification substantially updated, or alerted again.")
Will Brockman2b6959e2020-01-22 09:59:50 -050094 NOTIFICATION_UPDATED(163);
95
96 private final int mId;
Will Brockman75c60572020-01-31 10:30:27 -050097 NotificationReportedEvent(int id) {
Will Brockman2b6959e2020-01-22 09:59:50 -050098 mId = id;
99 }
100 @Override public int getId() {
101 return mId;
102 }
Will Brockman75c60572020-01-31 10:30:27 -0500103
104 public static NotificationReportedEvent fromRecordPair(NotificationRecordPair p) {
105 return (p.old != null) ? NotificationReportedEvent.NOTIFICATION_UPDATED :
106 NotificationReportedEvent.NOTIFICATION_POSTED;
107 }
108 }
109
110 enum NotificationCancelledEvent implements UiEventLogger.UiEventEnum {
111 INVALID(0),
112 @UiEvent(doc = "Notification was canceled due to a notification click.")
113 NOTIFICATION_CANCEL_CLICK(164),
114 @UiEvent(doc = "Notification was canceled due to a user dismissal, surface not specified.")
115 NOTIFICATION_CANCEL_USER_OTHER(165),
116 @UiEvent(doc = "Notification was canceled due to a user dismiss-all (from the notification"
117 + " shade).")
118 NOTIFICATION_CANCEL_USER_CANCEL_ALL(166),
119 @UiEvent(doc = "Notification was canceled due to an inflation error.")
120 NOTIFICATION_CANCEL_ERROR(167),
121 @UiEvent(doc = "Notification was canceled by the package manager modifying the package.")
122 NOTIFICATION_CANCEL_PACKAGE_CHANGED(168),
123 @UiEvent(doc = "Notification was canceled by the owning user context being stopped.")
124 NOTIFICATION_CANCEL_USER_STOPPED(169),
125 @UiEvent(doc = "Notification was canceled by the user banning the package.")
126 NOTIFICATION_CANCEL_PACKAGE_BANNED(170),
127 @UiEvent(doc = "Notification was canceled by the app canceling this specific notification.")
128 NOTIFICATION_CANCEL_APP_CANCEL(171),
129 @UiEvent(doc = "Notification was canceled by the app cancelling all its notifications.")
130 NOTIFICATION_CANCEL_APP_CANCEL_ALL(172),
131 @UiEvent(doc = "Notification was canceled by a listener reporting a user dismissal.")
132 NOTIFICATION_CANCEL_LISTENER_CANCEL(173),
133 @UiEvent(doc = "Notification was canceled by a listener reporting a user dismiss all.")
134 NOTIFICATION_CANCEL_LISTENER_CANCEL_ALL(174),
135 @UiEvent(doc = "Notification was canceled because it was a member of a canceled group.")
136 NOTIFICATION_CANCEL_GROUP_SUMMARY_CANCELED(175),
137 @UiEvent(doc = "Notification was canceled because it was an invisible member of a group.")
138 NOTIFICATION_CANCEL_GROUP_OPTIMIZATION(176),
139 @UiEvent(doc = "Notification was canceled by the device administrator suspending the "
140 + "package.")
141 NOTIFICATION_CANCEL_PACKAGE_SUSPENDED(177),
142 @UiEvent(doc = "Notification was canceled by the owning managed profile being turned off.")
143 NOTIFICATION_CANCEL_PROFILE_TURNED_OFF(178),
144 @UiEvent(doc = "Autobundled summary notification was canceled because its group was "
145 + "unbundled")
146 NOTIFICATION_CANCEL_UNAUTOBUNDLED(179),
147 @UiEvent(doc = "Notification was canceled by the user banning the channel.")
148 NOTIFICATION_CANCEL_CHANNEL_BANNED(180),
149 @UiEvent(doc = "Notification was snoozed.")
150 NOTIFICATION_CANCEL_SNOOZED(181),
151 @UiEvent(doc = "Notification was canceled due to timeout")
152 NOTIFICATION_CANCEL_TIMEOUT(182),
153 // Values 183-189 reserved for future system dismissal reasons
154 @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.")
155 NOTIFICATION_CANCEL_USER_PEEK(190),
156 @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
157 NOTIFICATION_CANCEL_USER_AOD(191),
158 @UiEvent(doc = "Notification was canceled due to user dismissal from the notification"
159 + " shade.")
160 NOTIFICATION_CANCEL_USER_SHADE(192),
161 @UiEvent(doc = "Notification was canceled due to user dismissal from the lockscreen")
162 NOTIFICATION_CANCEL_USER_LOCKSCREEN(193);
163
164 private final int mId;
165 NotificationCancelledEvent(int id) {
166 mId = id;
167 }
168 @Override public int getId() {
169 return mId;
170 }
Will Brockmand3d49332020-02-10 19:43:03 -0500171
Will Brockman75c60572020-01-31 10:30:27 -0500172 public static NotificationCancelledEvent fromCancelReason(
173 @NotificationListenerService.NotificationCancelReason int reason,
174 @NotificationStats.DismissalSurface int surface) {
175 // Shouldn't be possible to get a non-dismissed notification here.
176 if (surface == NotificationStats.DISMISSAL_NOT_DISMISSED) {
177 if (NotificationManagerService.DBG) {
178 throw new IllegalArgumentException("Unexpected surface " + surface);
179 }
180 return INVALID;
181 }
182 // Most cancel reasons do not have a meaningful surface. Reason codes map directly
183 // to NotificationCancelledEvent codes.
184 if (surface == NotificationStats.DISMISSAL_OTHER) {
185 if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
186 return NotificationCancelledEvent.values()[reason];
187 }
188 if (NotificationManagerService.DBG) {
189 throw new IllegalArgumentException("Unexpected cancel reason " + reason);
190 }
191 return INVALID;
192 }
193 // User cancels have a meaningful surface, which we differentiate by. See b/149038335
194 // for caveats.
195 if (reason != REASON_CANCEL) {
196 if (NotificationManagerService.DBG) {
197 throw new IllegalArgumentException("Unexpected cancel with surface " + reason);
198 }
199 return INVALID;
200 }
201 switch (surface) {
202 case NotificationStats.DISMISSAL_PEEK:
203 return NOTIFICATION_CANCEL_USER_PEEK;
204 case NotificationStats.DISMISSAL_AOD:
205 return NOTIFICATION_CANCEL_USER_AOD;
206 case NotificationStats.DISMISSAL_SHADE:
207 return NOTIFICATION_CANCEL_USER_SHADE;
208 default:
209 if (NotificationManagerService.DBG) {
210 throw new IllegalArgumentException("Unexpected surface for user-dismiss "
211 + reason);
212 }
213 return INVALID;
214 }
215 }
Will Brockman2b6959e2020-01-22 09:59:50 -0500216 }
217
Will Brockmand3d49332020-02-10 19:43:03 -0500218 enum NotificationEvent implements UiEventLogger.UiEventEnum {
219 @UiEvent(doc = "Notification became visible.")
220 NOTIFICATION_OPEN(197),
221 @UiEvent(doc = "Notification stopped being visible.")
Will Brockman9918db92020-03-06 16:48:39 -0500222 NOTIFICATION_CLOSE(198),
223 @UiEvent(doc = "Notification was snoozed.")
224 NOTIFICATION_SNOOZED(317),
225 @UiEvent(doc = "Notification was not posted because its app is snoozed.")
226 NOTIFICATION_NOT_POSTED_SNOOZED(319),
227 @UiEvent(doc = "Notification was clicked.")
228 NOTIFICATION_CLICKED(320),
229 @UiEvent(doc = "Notification action was clicked.")
230 NOTIFICATION_ACTION_CLICKED(321),
231 @UiEvent(doc = "Notification detail was expanded due to non-user action.")
232 NOTIFICATION_DETAIL_OPEN_SYSTEM(327),
233 @UiEvent(doc = "Notification detail was collapsed due to non-user action.")
234 NOTIFICATION_DETAIL_CLOSE_SYSTEM(328),
235 @UiEvent(doc = "Notification detail was expanded due to user action.")
236 NOTIFICATION_DETAIL_OPEN_USER(329),
237 @UiEvent(doc = "Notification detail was collapsed due to user action.")
238 NOTIFICATION_DETAIL_CLOSE_USER(330),
239 @UiEvent(doc = "Notification direct reply action was used.")
240 NOTIFICATION_DIRECT_REPLIED(331),
241 @UiEvent(doc = "Notification smart reply action was used.")
242 NOTIFICATION_SMART_REPLIED(332),
243 @UiEvent(doc = "Notification smart reply action was visible.")
244 NOTIFICATION_SMART_REPLY_VISIBLE(333),
245 ;
Will Brockmand3d49332020-02-10 19:43:03 -0500246
247 private final int mId;
248 NotificationEvent(int id) {
249 mId = id;
250 }
251 @Override public int getId() {
252 return mId;
253 }
254
255 public static NotificationEvent fromVisibility(boolean visible) {
256 return visible ? NOTIFICATION_OPEN : NOTIFICATION_CLOSE;
257 }
Will Brockman9918db92020-03-06 16:48:39 -0500258 public static NotificationEvent fromExpanded(boolean expanded, boolean userAction) {
259 if (userAction) {
260 return expanded ? NOTIFICATION_DETAIL_OPEN_USER : NOTIFICATION_DETAIL_CLOSE_USER;
261 }
262 return expanded ? NOTIFICATION_DETAIL_OPEN_SYSTEM : NOTIFICATION_DETAIL_CLOSE_SYSTEM;
263 }
Will Brockmand3d49332020-02-10 19:43:03 -0500264 }
Will Brockman9918db92020-03-06 16:48:39 -0500265
266 enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
267 @UiEvent(doc = "Notification panel became visible.")
268 NOTIFICATION_PANEL_OPEN(325),
269 @UiEvent(doc = "Notification panel stopped being visible.")
270 NOTIFICATION_PANEL_CLOSE(326);
271
272 private final int mId;
273 NotificationPanelEvent(int id) {
274 mId = id;
275 }
276 @Override public int getId() {
277 return mId;
278 }
279 }
280
Will Brockman2b6959e2020-01-22 09:59:50 -0500281 /**
282 * A helper for extracting logging information from one or two NotificationRecords.
283 */
284 class NotificationRecordPair {
285 public final NotificationRecord r, old;
286 /**
287 * Construct from one or two NotificationRecords.
288 * @param r The new NotificationRecord. If null, only shouldLog() method is usable.
289 * @param old The previous NotificationRecord. Null if there was no previous record.
290 */
291 NotificationRecordPair(@Nullable NotificationRecord r, @Nullable NotificationRecord old) {
292 this.r = r;
293 this.old = old;
294 }
295
296 /**
297 * @return True if old is null, alerted, or important logged fields have changed.
298 */
Will Brockmand3d49332020-02-10 19:43:03 -0500299 boolean shouldLogReported(int buzzBeepBlink) {
Will Brockman2b6959e2020-01-22 09:59:50 -0500300 if (r == null) {
301 return false;
302 }
303 if ((old == null) || (buzzBeepBlink > 0)) {
304 return true;
305 }
306
Will Brockman75c60572020-01-31 10:30:27 -0500307 return !(Objects.equals(r.getSbn().getChannelIdLogTag(),
308 old.getSbn().getChannelIdLogTag())
Julia Reynolds24edc002020-01-29 16:35:32 -0500309 && Objects.equals(r.getSbn().getGroupLogTag(), old.getSbn().getGroupLogTag())
310 && (r.getSbn().getNotification().isGroupSummary()
311 == old.getSbn().getNotification().isGroupSummary())
312 && Objects.equals(r.getSbn().getNotification().category,
313 old.getSbn().getNotification().category)
Will Brockman2b6959e2020-01-22 09:59:50 -0500314 && (r.getImportance() == old.getImportance()));
315 }
316
Will Brockman2b6959e2020-01-22 09:59:50 -0500317 /**
318 * @return hash code for the notification style class, or 0 if none exists.
319 */
320 public int getStyle() {
Julia Reynolds24edc002020-01-29 16:35:32 -0500321 return getStyle(r.getSbn().getNotification().extras);
Will Brockman2b6959e2020-01-22 09:59:50 -0500322 }
323
324 private int getStyle(@Nullable Bundle extras) {
325 if (extras != null) {
326 String template = extras.getString(Notification.EXTRA_TEMPLATE);
327 if (template != null && !template.isEmpty()) {
328 return template.hashCode();
329 }
330 }
331 return 0;
332 }
333
334 int getNumPeople() {
Julia Reynolds24edc002020-01-29 16:35:32 -0500335 return getNumPeople(r.getSbn().getNotification().extras);
Will Brockman2b6959e2020-01-22 09:59:50 -0500336 }
337
338 private int getNumPeople(@Nullable Bundle extras) {
339 if (extras != null) {
340 ArrayList<Person> people = extras.getParcelableArrayList(
341 Notification.EXTRA_PEOPLE_LIST);
342 if (people != null && !people.isEmpty()) {
343 return people.size();
344 }
345 }
346 return 0;
347 }
348
349 int getAssistantHash() {
350 String assistant = r.getAdjustmentIssuer();
351 return (assistant == null) ? 0 : assistant.hashCode();
352 }
Will Brockmancfd98302020-01-29 15:57:30 -0500353
354 int getInstanceId() {
Julia Reynolds24edc002020-01-29 16:35:32 -0500355 return (r.getSbn().getInstanceId() == null ? 0 : r.getSbn().getInstanceId().getId());
Will Brockmancfd98302020-01-29 15:57:30 -0500356 }
Will Brockman7614f6962020-02-10 19:43:03 -0500357
358 /**
359 * @return Small hash of the notification ID, and tag (if present).
360 */
361 int getNotificationIdHash() {
Will Brockmana83de9e2020-03-26 13:42:18 -0400362 return SmallHash.hash(Objects.hashCode(r.getSbn().getTag()) ^ r.getSbn().getId());
Will Brockman7614f6962020-02-10 19:43:03 -0500363 }
364
365 /**
366 * @return Small hash of the channel ID, if present, or 0 otherwise.
367 */
368 int getChannelIdHash() {
Will Brockmana83de9e2020-03-26 13:42:18 -0400369 return SmallHash.hash(r.getSbn().getNotification().getChannelId());
Will Brockman7614f6962020-02-10 19:43:03 -0500370 }
371
372 /**
373 * @return Small hash of the group ID, respecting group override if present. 0 otherwise.
374 */
375 int getGroupIdHash() {
Will Brockmana83de9e2020-03-26 13:42:18 -0400376 return SmallHash.hash(r.getSbn().getGroup());
Will Brockman7614f6962020-02-10 19:43:03 -0500377 }
378
Will Brockman2b6959e2020-01-22 09:59:50 -0500379 }
Will Brockman23db6d42020-02-28 09:51:12 -0500380
Will Brockman23db6d42020-02-28 09:51:12 -0500381
Will Brockman2b6959e2020-01-22 09:59:50 -0500382}