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