1 /*
<lambda>null2  * Copyright (C) 2022 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.service.notification.NotificationListenerService.RankingMap
20 import android.util.ArrayMap
21 import com.android.internal.annotations.VisibleForTesting
22 import com.android.systemui.statusbar.notification.collection.NotificationEntry
23 import com.android.systemui.util.asIndenting
24 import com.android.systemui.util.printCollection
25 import java.io.PrintWriter
26 
27 class NotifCollectionInconsistencyTracker(val logger: NotifCollectionLogger) {
28     fun attach(
29         collectedKeySetAccessor: () -> Set<String>,
30         coalescedKeySetAccessor: () -> Set<String>,
31     ) {
32         if (attached) {
33             throw RuntimeException("attach() called twice")
34         }
35         attached = true
36         this.collectedKeySetAccessor = collectedKeySetAccessor
37         this.coalescedKeySetAccessor = coalescedKeySetAccessor
38     }
39 
40     fun logNewMissingNotifications(rankingMap: RankingMap) {
41         val currentCollectedKeys = collectedKeySetAccessor()
42         val currentCoalescedKeys = coalescedKeySetAccessor()
43         val newMissingNotifications = rankingMap.orderedKeys.asSequence()
44             .filter { it !in currentCollectedKeys }
45             .filter { it !in currentCoalescedKeys }
46             .toSet()
47         maybeLogMissingNotifications(missingNotifications, newMissingNotifications)
48         missingNotifications = newMissingNotifications
49     }
50 
51     @VisibleForTesting
52     fun maybeLogMissingNotifications(
53         oldMissingKeys: Set<String>,
54         newMissingKeys: Set<String>,
55     ) {
56         if (oldMissingKeys.isEmpty() && newMissingKeys.isEmpty()) return
57         if (oldMissingKeys == newMissingKeys) return
58         (oldMissingKeys - newMissingKeys).sorted().let { justFound ->
59             if (justFound.isNotEmpty()) {
60                 logger.logFoundNotifications(justFound, newMissingKeys.size)
61             }
62         }
63         (newMissingKeys - oldMissingKeys).sorted().let { goneMissing ->
64             if (goneMissing.isNotEmpty()) {
65                 logger.logMissingNotifications(goneMissing, newMissingKeys.size)
66             }
67         }
68     }
69 
70     fun logNewInconsistentRankings(
71         currentEntriesWithoutRankings: ArrayMap<String, NotificationEntry>?,
72         rankingMap: RankingMap,
73     ) {
74         maybeLogInconsistentRankings(
75             notificationsWithoutRankings,
76             currentEntriesWithoutRankings ?: emptyMap(),
77             rankingMap
78         )
79         notificationsWithoutRankings = currentEntriesWithoutRankings?.keys ?: emptySet()
80     }
81 
82     @VisibleForTesting
83     fun maybeLogInconsistentRankings(
84         oldKeysWithoutRankings: Set<String>,
85         newEntriesWithoutRankings: Map<String, NotificationEntry>,
86         rankingMap: RankingMap,
87     ) {
88         if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings.isEmpty()) return
89         if (oldKeysWithoutRankings == newEntriesWithoutRankings.keys) return
90         val newlyConsistent: List<String> = oldKeysWithoutRankings
91             .mapNotNull { key ->
92                 key.takeIf { key !in newEntriesWithoutRankings }
93                     .takeIf { key in rankingMap.orderedKeys }
94             }.sorted()
95         if (newlyConsistent.isNotEmpty()) {
96             val totalInconsistent: Int = newEntriesWithoutRankings.size
97             logger.logRecoveredRankings(newlyConsistent, totalInconsistent)
98         }
99         val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
100             .mapNotNull { (key, entry) ->
101                 entry.takeIf { key !in oldKeysWithoutRankings }
102             }.sortedBy { it.key }
103         if (newlyInconsistent.isNotEmpty()) {
104             val totalInconsistent: Int = newEntriesWithoutRankings.size
105             logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
106         }
107     }
108 
109     fun dump(pw: PrintWriter) = pw.asIndenting().run {
110         printCollection("notificationsWithoutRankings", notificationsWithoutRankings)
111         printCollection("missingNotifications", missingNotifications)
112     }
113 
114     private var attached: Boolean = false
115     private lateinit var collectedKeySetAccessor: (() -> Set<String>)
116     private lateinit var coalescedKeySetAccessor: (() -> Set<String>)
117     private var notificationsWithoutRankings = emptySet<String>()
118     private var missingNotifications = emptySet<String>()
119 }
120