blob: 8bce5b011d0cb7ca5f548ab8e3c9be8a4f54de69 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.collection.notifcollection
import android.service.notification.NotificationListenerService.RankingMap
import android.util.ArrayMap
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import java.io.PrintWriter
class NotifCollectionInconsistencyTracker(val logger: NotifCollectionLogger) {
fun attach(
collectedKeySetAccessor: () -> Set<String>,
coalescedKeySetAccessor: () -> Set<String>,
) {
if (attached) {
throw RuntimeException("attach() called twice")
}
attached = true
this.collectedKeySetAccessor = collectedKeySetAccessor
this.coalescedKeySetAccessor = coalescedKeySetAccessor
}
fun logNewMissingNotifications(rankingMap: RankingMap) {
val currentCollectedKeys = collectedKeySetAccessor()
val currentCoalescedKeys = coalescedKeySetAccessor()
val newMissingNotifications = rankingMap.orderedKeys.asSequence()
.filter { it !in currentCollectedKeys }
.filter { it !in currentCoalescedKeys }
.toSet()
maybeLogMissingNotifications(missingNotifications, newMissingNotifications)
missingNotifications = newMissingNotifications
}
@VisibleForTesting
fun maybeLogMissingNotifications(
oldMissingKeys: Set<String>,
newMissingKeys: Set<String>,
) {
if (oldMissingKeys.isEmpty() && newMissingKeys.isEmpty()) return
if (oldMissingKeys == newMissingKeys) return
(oldMissingKeys - newMissingKeys).sorted().let { justFound ->
if (justFound.isNotEmpty()) {
logger.logFoundNotifications(justFound, newMissingKeys.size)
}
}
(newMissingKeys - oldMissingKeys).sorted().let { goneMissing ->
if (goneMissing.isNotEmpty()) {
logger.logMissingNotifications(goneMissing, newMissingKeys.size)
}
}
}
fun logNewInconsistentRankings(
currentEntriesWithoutRankings: ArrayMap<String, NotificationEntry>?,
rankingMap: RankingMap,
) {
maybeLogInconsistentRankings(
notificationsWithoutRankings,
currentEntriesWithoutRankings ?: emptyMap(),
rankingMap
)
notificationsWithoutRankings = currentEntriesWithoutRankings?.keys ?: emptySet()
}
@VisibleForTesting
fun maybeLogInconsistentRankings(
oldKeysWithoutRankings: Set<String>,
newEntriesWithoutRankings: Map<String, NotificationEntry>,
rankingMap: RankingMap,
) {
if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings.isEmpty()) return
if (oldKeysWithoutRankings == newEntriesWithoutRankings.keys) return
val newlyConsistent: List<String> = oldKeysWithoutRankings
.mapNotNull { key ->
key.takeIf { key !in newEntriesWithoutRankings }
.takeIf { key in rankingMap.orderedKeys }
}.sorted()
if (newlyConsistent.isNotEmpty()) {
val totalInconsistent: Int = newEntriesWithoutRankings.size
logger.logRecoveredRankings(newlyConsistent, totalInconsistent)
}
val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
.mapNotNull { (key, entry) ->
entry.takeIf { key !in oldKeysWithoutRankings }
}.sortedBy { it.key }
if (newlyInconsistent.isNotEmpty()) {
val totalInconsistent: Int = newEntriesWithoutRankings.size
logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
}
}
fun dump(pw: PrintWriter) {
pw.println("notificationsWithoutRankings: ${notificationsWithoutRankings.size}")
for (key in notificationsWithoutRankings) {
pw.println("\t * : $key")
}
pw.println("missingNotifications: ${missingNotifications.size}")
for (key in missingNotifications) {
pw.println("\t * : $key")
}
}
private var attached: Boolean = false
private lateinit var collectedKeySetAccessor: (() -> Set<String>)
private lateinit var coalescedKeySetAccessor: (() -> Set<String>)
private var notificationsWithoutRankings = emptySet<String>()
private var missingNotifications = emptySet<String>()
}