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.systemui.statusbar.notification.collection.notifcollection
18 
19 import android.os.RemoteException
20 import android.service.notification.NotificationListenerService
21 import android.service.notification.NotificationListenerService.RankingMap
22 import android.service.notification.StatusBarNotification
23 import com.android.systemui.log.dagger.NotificationLog
24 import com.android.systemui.log.LogBuffer
25 import com.android.systemui.log.core.LogLevel.DEBUG
26 import com.android.systemui.log.core.LogLevel.ERROR
27 import com.android.systemui.log.core.LogLevel.INFO
28 import com.android.systemui.log.core.LogLevel.WARNING
29 import com.android.systemui.log.core.LogLevel.WTF
30 import com.android.systemui.statusbar.notification.collection.NotifCollection
31 import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason
32 import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal
33 import com.android.systemui.statusbar.notification.collection.NotificationEntry
34 import com.android.systemui.statusbar.notification.logKey
35 import javax.inject.Inject
36 
cancellationReasonDebugStringnull37 fun cancellationReasonDebugString(@CancellationReason reason: Int) =
38     "$reason:" + when (reason) {
39         -1 -> "REASON_NOT_CANCELED" // NotifCollection.REASON_NOT_CANCELED
40         NotifCollection.REASON_UNKNOWN -> "REASON_UNKNOWN"
41         NotificationListenerService.REASON_CLICK -> "REASON_CLICK"
42         NotificationListenerService.REASON_CANCEL -> "REASON_CANCEL"
43         NotificationListenerService.REASON_CANCEL_ALL -> "REASON_CANCEL_ALL"
44         NotificationListenerService.REASON_ERROR -> "REASON_ERROR"
45         NotificationListenerService.REASON_PACKAGE_CHANGED -> "REASON_PACKAGE_CHANGED"
46         NotificationListenerService.REASON_USER_STOPPED -> "REASON_USER_STOPPED"
47         NotificationListenerService.REASON_PACKAGE_BANNED -> "REASON_PACKAGE_BANNED"
48         NotificationListenerService.REASON_APP_CANCEL -> "REASON_APP_CANCEL"
49         NotificationListenerService.REASON_APP_CANCEL_ALL -> "REASON_APP_CANCEL_ALL"
50         NotificationListenerService.REASON_LISTENER_CANCEL -> "REASON_LISTENER_CANCEL"
51         NotificationListenerService.REASON_LISTENER_CANCEL_ALL -> "REASON_LISTENER_CANCEL_ALL"
52         NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED -> "REASON_GROUP_SUMMARY_CANCELED"
53         NotificationListenerService.REASON_GROUP_OPTIMIZATION -> "REASON_GROUP_OPTIMIZATION"
54         NotificationListenerService.REASON_PACKAGE_SUSPENDED -> "REASON_PACKAGE_SUSPENDED"
55         NotificationListenerService.REASON_PROFILE_TURNED_OFF -> "REASON_PROFILE_TURNED_OFF"
56         NotificationListenerService.REASON_UNAUTOBUNDLED -> "REASON_UNAUTOBUNDLED"
57         NotificationListenerService.REASON_CHANNEL_BANNED -> "REASON_CHANNEL_BANNED"
58         NotificationListenerService.REASON_SNOOZED -> "REASON_SNOOZED"
59         NotificationListenerService.REASON_TIMEOUT -> "REASON_TIMEOUT"
60         NotificationListenerService.REASON_CHANNEL_REMOVED -> "REASON_CHANNEL_REMOVED"
61         NotificationListenerService.REASON_CLEAR_DATA -> "REASON_CLEAR_DATA"
62         NotificationListenerService.REASON_ASSISTANT_CANCEL -> "REASON_ASSISTANT_CANCEL"
63         else -> "unknown"
64     }
65 
66 class NotifCollectionLogger @Inject constructor(
67     @NotificationLog private val buffer: LogBuffer
68 ) {
logNotifPostednull69     fun logNotifPosted(entry: NotificationEntry) {
70         buffer.log(TAG, INFO, {
71             str1 = entry.logKey
72         }, {
73             "POSTED $str1"
74         })
75     }
76 
logNotifGroupPostednull77     fun logNotifGroupPosted(groupKey: String, batchSize: Int) {
78         buffer.log(TAG, INFO, {
79             str1 = logKey(groupKey)
80             int1 = batchSize
81         }, {
82             "POSTED GROUP $str1 ($int1 events)"
83         })
84     }
85 
logNotifUpdatednull86     fun logNotifUpdated(entry: NotificationEntry) {
87         buffer.log(TAG, INFO, {
88             str1 = entry.logKey
89         }, {
90             "UPDATED $str1"
91         })
92     }
93 
logNotifRemovednull94     fun logNotifRemoved(sbn: StatusBarNotification, @CancellationReason reason: Int) {
95         buffer.log(TAG, INFO, {
96             str1 = sbn.logKey
97             int1 = reason
98         }, {
99             "REMOVED $str1 reason=${cancellationReasonDebugString(int1)}"
100         })
101     }
102 
logNotifReleasednull103     fun logNotifReleased(entry: NotificationEntry) {
104         buffer.log(TAG, INFO, {
105             str1 = entry.logKey
106         }, {
107             "RELEASED $str1"
108         })
109     }
110 
logLocallyDismissednull111     fun logLocallyDismissed(entry: NotificationEntry, index: Int, count: Int) {
112         buffer.log(TAG, INFO, {
113             str1 = entry.logKey
114             int1 = index
115             int2 = count
116         }, {
117             "LOCALLY DISMISSED $str1 ($int1/$int2)"
118         })
119     }
120 
logDismissNonExistentNotifnull121     fun logDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
122         buffer.log(TAG, INFO, {
123             str1 = entry.logKey
124             int1 = index
125             int2 = count
126         }, {
127             "DISMISS Non Existent $str1 ($int1/$int2)"
128         })
129     }
130 
logLocallyDismissedChildnull131     fun logLocallyDismissedChild(
132             child: NotificationEntry,
133             parent: NotificationEntry,
134             parentIndex: Int,
135             parentCount: Int
136     ) {
137         buffer.log(TAG, DEBUG, {
138             str1 = child.logKey
139             str2 = parent.logKey
140             int1 = parentIndex
141             int2 = parentCount
142         }, {
143             "LOCALLY DISMISSED CHILD (inferred): $str1 of parent $str2 ($int1/$int2)"
144         })
145     }
146 
logDismissAllnull147     fun logDismissAll(userId: Int) {
148         buffer.log(TAG, INFO, {
149             int1 = userId
150         }, {
151             "DISMISS ALL notifications for user $int1"
152         })
153     }
154 
logLocallyDismissedAlreadyCanceledEntrynull155     fun logLocallyDismissedAlreadyCanceledEntry(entry: NotificationEntry) {
156         buffer.log(TAG, DEBUG, {
157             str1 = entry.logKey
158         }, {
159             "LOCALLY DISMISSED Already Canceled $str1. Trying to remove."
160         })
161     }
162 
logNotifDismissedInterceptednull163     fun logNotifDismissedIntercepted(entry: NotificationEntry, index: Int, count: Int) {
164         buffer.log(TAG, INFO, {
165             str1 = entry.logKey
166             int1 = index
167             int2 = count
168         }, {
169             "DISMISS INTERCEPTED $str1 ($int1/$int2)"
170         })
171     }
172 
logNotifClearAllDismissalInterceptednull173     fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry, index: Int, count: Int) {
174         buffer.log(TAG, INFO, {
175             str1 = entry.logKey
176             int1 = index
177             int2 = count
178         }, {
179             "CLEAR ALL DISMISSAL INTERCEPTED $str1 ($int1/$int2)"
180         })
181     }
182 
logNotifInternalUpdatenull183     fun logNotifInternalUpdate(entry: NotificationEntry, name: String, reason: String) {
184         buffer.log(TAG, INFO, {
185             str1 = entry.logKey
186             str2 = name
187             str3 = reason
188         }, {
189             "UPDATED INTERNALLY $str1 BY $str2 BECAUSE $str3"
190         })
191     }
192 
logNotifInternalUpdateFailednull193     fun logNotifInternalUpdateFailed(sbn: StatusBarNotification, name: String, reason: String) {
194         buffer.log(TAG, INFO, {
195             str1 = sbn.logKey
196             str2 = name
197             str3 = reason
198         }, {
199             "FAILED INTERNAL UPDATE $str1 BY $str2 BECAUSE $str3"
200         })
201     }
202 
logNoNotificationToRemoveWithKeynull203     fun logNoNotificationToRemoveWithKey(
204         sbn: StatusBarNotification,
205         @CancellationReason reason: Int
206     ) {
207         buffer.log(TAG, ERROR, {
208             str1 = sbn.logKey
209             int1 = reason
210         }, {
211             "No notification to remove with key $str1 reason=${cancellationReasonDebugString(int1)}"
212         })
213     }
214 
logMissingRankingsnull215     fun logMissingRankings(
216         newlyInconsistentEntries: List<NotificationEntry>,
217         totalInconsistent: Int,
218         rankingMap: RankingMap
219     ) {
220         buffer.log(TAG, WARNING, {
221             int1 = totalInconsistent
222             int2 = newlyInconsistentEntries.size
223             str1 = newlyInconsistentEntries.joinToString { it.logKey ?: "null" }
224         }, {
225             "Ranking update is missing ranking for $int1 entries ($int2 new): $str1"
226         })
227         buffer.log(TAG, DEBUG, {
228             str1 = rankingMap.orderedKeys.map { logKey(it) ?: "null" }.toString()
229         }, {
230             "Ranking map contents: $str1"
231         })
232     }
233 
logRecoveredRankingsnull234     fun logRecoveredRankings(newlyConsistentKeys: List<String>, totalInconsistent: Int) {
235         buffer.log(TAG, INFO, {
236             int1 = totalInconsistent
237             int1 = newlyConsistentKeys.size
238             str1 = newlyConsistentKeys.joinToString { logKey(it) ?: "null" }
239         }, {
240             "Ranking update now contains rankings for $int1 previously inconsistent entries: $str1"
241         })
242     }
243 
logMissingNotificationsnull244     fun logMissingNotifications(
245         newlyMissingKeys: List<String>,
246         totalMissing: Int,
247     ) {
248         buffer.log(TAG, WARNING, {
249             int1 = totalMissing
250             int2 = newlyMissingKeys.size
251             str1 = newlyMissingKeys.joinToString { logKey(it) ?: "null" }
252         }, {
253             "Collection missing $int1 entries in ranking update. Just lost $int2: $str1"
254         })
255     }
256 
logFoundNotificationsnull257     fun logFoundNotifications(
258         newlyFoundKeys: List<String>,
259         totalMissing: Int,
260     ) {
261         buffer.log(TAG, INFO, {
262             int1 = totalMissing
263             int2 = newlyFoundKeys.size
264             str1 = newlyFoundKeys.joinToString { logKey(it) ?: "null" }
265         }, {
266             "Collection missing $int1 entries in ranking update. Just found $int2: $str1"
267         })
268     }
269 
logRemoteExceptionOnNotificationClearnull270     fun logRemoteExceptionOnNotificationClear(
271             entry: NotificationEntry,
272             index: Int,
273             count: Int,
274             e: RemoteException
275     ) {
276         buffer.log(TAG, WTF, {
277             str1 = entry.logKey
278             int1 = index
279             int2 = count
280             str2 = e.toString()
281         }, {
282             "RemoteException while attempting to clear $str1 ($int1/$int2):\n$str2"
283         })
284     }
285 
logRemoteExceptionOnClearAllNotificationsnull286     fun logRemoteExceptionOnClearAllNotifications(e: RemoteException) {
287         buffer.log(TAG, WTF, {
288             str1 = e.toString()
289         }, {
290             "RemoteException while attempting to clear all notifications:\n$str1"
291         })
292     }
293 
logLifetimeExtendednull294     fun logLifetimeExtended(entry: NotificationEntry, extender: NotifLifetimeExtender) {
295         buffer.log(TAG, INFO, {
296             str1 = entry.logKey
297             str2 = extender.name
298         }, {
299             "LIFETIME EXTENDED: $str1 by $str2"
300         })
301     }
302 
logLifetimeExtensionEndednull303     fun logLifetimeExtensionEnded(
304         entry: NotificationEntry,
305         extender: NotifLifetimeExtender,
306         totalExtenders: Int
307     ) {
308         buffer.log(TAG, INFO, {
309             str1 = entry.logKey
310             str2 = extender.name
311             int1 = totalExtenders
312         }, {
313             "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions"
314         })
315     }
316 
logIgnoredErrornull317     fun logIgnoredError(message: String?) {
318         buffer.log(TAG, ERROR, {
319             str1 = message
320         }, {
321             "ERROR suppressed due to initialization forgiveness: $str1"
322         })
323     }
324 
logEntryBeingExtendedNotInCollectionnull325     fun logEntryBeingExtendedNotInCollection(
326         entry: NotificationEntry,
327         extender: NotifLifetimeExtender,
328         collectionEntryIs: String
329     ) {
330         buffer.log(TAG, WARNING, {
331             str1 = entry.logKey
332             str2 = extender.name
333             str3 = collectionEntryIs
334         }, {
335             "While ending lifetime extension by $str2 of $str1, entry in collection is $str3"
336         })
337     }
338 
logFutureDismissalReusednull339     fun logFutureDismissalReused(dismissal: FutureDismissal) {
340         buffer.log(TAG, INFO, {
341             str1 = dismissal.label
342         }, {
343             "Reusing existing registration: $str1"
344         })
345     }
346 
logFutureDismissalRegisterednull347     fun logFutureDismissalRegistered(dismissal: FutureDismissal) {
348         buffer.log(TAG, DEBUG, {
349             str1 = dismissal.label
350         }, {
351             "Registered: $str1"
352         })
353     }
354 
logFutureDismissalDoubleCancelledByServernull355     fun logFutureDismissalDoubleCancelledByServer(dismissal: FutureDismissal) {
356         buffer.log(TAG, WARNING, {
357             str1 = dismissal.label
358         }, {
359             "System server double cancelled: $str1"
360         })
361     }
362 
logFutureDismissalDoubleRunnull363     fun logFutureDismissalDoubleRun(dismissal: FutureDismissal) {
364         buffer.log(TAG, WARNING, {
365             str1 = dismissal.label
366         }, {
367             "Double run: $str1"
368         })
369     }
370 
logFutureDismissalAlreadyCancelledByServernull371     fun logFutureDismissalAlreadyCancelledByServer(dismissal: FutureDismissal) {
372         buffer.log(TAG, DEBUG, {
373             str1 = dismissal.label
374         }, {
375             "Ignoring: entry already cancelled by server: $str1"
376         })
377     }
378 
logFutureDismissalGotSystemServerCancelnull379     fun logFutureDismissalGotSystemServerCancel(
380         dismissal: FutureDismissal,
381         @CancellationReason cancellationReason: Int
382     ) {
383         buffer.log(TAG, DEBUG, {
384             str1 = dismissal.label
385             int1 = cancellationReason
386         }, {
387             "SystemServer cancelled: $str1 reason=${cancellationReasonDebugString(int1)}"
388         })
389     }
390 
logFutureDismissalDismissingnull391     fun logFutureDismissalDismissing(dismissal: FutureDismissal, type: String) {
392         buffer.log(TAG, DEBUG, {
393             str1 = dismissal.label
394             str2 = type
395         }, {
396             "Dismissing $str2 for: $str1"
397         })
398     }
399 
logFutureDismissalMismatchedEntrynull400     fun logFutureDismissalMismatchedEntry(
401         dismissal: FutureDismissal,
402         type: String,
403         latestEntry: NotificationEntry?
404     ) {
405         buffer.log(TAG, WARNING, {
406             str1 = dismissal.label
407             str2 = type
408             str3 = latestEntry.logKey
409         }, {
410             "Mismatch: current $str2 is $str3 for: $str1"
411         })
412     }
413 
logDismissAlreadyDismissedNotifnull414     fun logDismissAlreadyDismissedNotif(entry: NotificationEntry, index: Int, count: Int) {
415         buffer.log(TAG, DEBUG, {
416             str1 = entry.logKey
417             int1 = index
418             int2 = count
419         }, {
420             "DISMISS Already Dismissed $str1 ($int1/$int2)"
421         })
422     }
423 
logDismissAlreadyParentDismissedNotifnull424     fun logDismissAlreadyParentDismissedNotif(
425             childEntry: NotificationEntry,
426             childIndex: Int,
427             childCount: Int
428     ) {
429         buffer.log(TAG, DEBUG, {
430             str1 = childEntry.logKey
431             int1 = childIndex
432             int2 = childCount
433             str2 = childEntry.parent?.summary?.logKey ?: "(null)"
434         }, {
435             "DISMISS Already Parent-Dismissed $str1 ($int1/$int2) with summary $str2"
436         })
437     }
438 
logLocallyDismissNonExistentNotifnull439     fun logLocallyDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
440         buffer.log(TAG, INFO, {
441             str1 = entry.logKey
442             int1 = index
443             int2 = count
444         }, {
445             "LOCALLY DISMISS Non Existent $str1 ($int1/$int2)"
446         })
447     }
448 
logLocallyDismissMismatchedEntrynull449     fun logLocallyDismissMismatchedEntry(
450             entry: NotificationEntry,
451             index: Int,
452             count: Int,
453             storedEntry: NotificationEntry
454     ) {
455         buffer.log(TAG, INFO, {
456             str1 = entry.logKey
457             int1 = index
458             int2 = count
459             str2 = Integer.toHexString(entry.hashCode())
460             str3 = Integer.toHexString(storedEntry.hashCode())
461         }, {
462             "LOCALLY DISMISS Mismatch $str1 ($int1/$int2): dismissing @$str2 but stored @$str3"
463         })
464     }
465 
logLocallyDismissAlreadyDismissedNotifnull466     fun logLocallyDismissAlreadyDismissedNotif(
467             entry: NotificationEntry,
468             index: Int,
469             count: Int
470     ) {
471         buffer.log(TAG, INFO, {
472             str1 = entry.logKey
473             int1 = index
474             int2 = count
475         }, {
476             "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
477         })
478     }
479 
logLocallyDismissAlreadyParentDismissedNotifnull480     fun logLocallyDismissAlreadyParentDismissedNotif(
481             entry: NotificationEntry,
482             index: Int,
483             count: Int
484     ) {
485         buffer.log(TAG, INFO, {
486             str1 = entry.logKey
487             int1 = index
488             int2 = count
489         }, {
490             "LOCALLY DISMISS Already Dismissed $str1 ($int1/$int2)"
491         })
492     }
493 
logLocallyDismissAlreadyDismissedChildnull494     fun logLocallyDismissAlreadyDismissedChild(
495             childEntry: NotificationEntry,
496             parentEntry: NotificationEntry,
497             parentIndex: Int,
498             parentCount: Int
499     ) {
500         buffer.log(TAG, INFO, {
501             str1 = childEntry.logKey
502             str2 = parentEntry.logKey
503             int1 = parentIndex
504             int2 = parentCount
505         }, {
506             "LOCALLY DISMISS Already Dismissed Child $str1 of parent $str2 ($int1/$int2)"
507         })
508     }
509 
logLocallyDismissAlreadyParentDismissedChildnull510     fun logLocallyDismissAlreadyParentDismissedChild(
511             childEntry: NotificationEntry,
512             parentEntry: NotificationEntry,
513             parentIndex: Int,
514             parentCount: Int
515     ) {
516         buffer.log(TAG, INFO, {
517             str1 = childEntry.logKey
518             str2 = parentEntry.logKey
519             int1 = parentIndex
520             int2 = parentCount
521         }, {
522             "LOCALLY DISMISS Already Parent-Dismissed Child $str1 of parent $str2 ($int1/$int2)"
523         })
524     }
525 
logCancelLocalDismissalNotDismissedNotifnull526     fun logCancelLocalDismissalNotDismissedNotif(entry: NotificationEntry) {
527         buffer.log(TAG, INFO, {
528             str1 = entry.logKey
529         }, {
530             "CANCEL LOCAL DISMISS Not Dismissed $str1"
531         })
532     }
533 }
534 
535 private const val TAG = "NotifCollection"
536