1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.systemui.statusbar.notification.collection.coordinator
17 
18 import android.util.ArrayMap
19 import com.android.systemui.dagger.qualifiers.Main
20 import com.android.systemui.statusbar.notification.collection.GroupEntry
21 import com.android.systemui.statusbar.notification.collection.ListEntry
22 import com.android.systemui.statusbar.notification.collection.NotifPipeline
23 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
24 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
25 import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
26 import com.android.systemui.util.concurrency.DelayableExecutor
27 import com.android.systemui.util.time.SystemClock
28 import javax.inject.Inject
29 import kotlin.math.max
30 import kotlin.math.min
31 
32 /** A small coordinator which finds, stores, and applies the closest notification time. */
33 @CoordinatorScope
34 class GroupWhenCoordinator
35 @Inject
36 constructor(
37     @Main private val delayableExecutor: DelayableExecutor,
38     private val systemClock: SystemClock
39 ) : Coordinator {
40 
41     private val invalidator = object : Invalidator("GroupWhenCoordinator") {}
42     private val notificationGroupTimes = ArrayMap<GroupEntry, Long>()
43     private var cancelInvalidateListRunnable: Runnable? = null
44 
45     private val invalidateListRunnable: Runnable = Runnable {
46         invalidator.invalidateList("future notification invalidation")
47     }
48 
49     override fun attach(pipeline: NotifPipeline) {
50         pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
51         pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener)
52         pipeline.addPreRenderInvalidator(invalidator)
53     }
54 
55     private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
56         cancelListInvalidation()
57         notificationGroupTimes.clear()
58 
59         val now = systemClock.currentTimeMillis()
60         var closestFutureTime = Long.MAX_VALUE
61         entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
62             val whenMillis = calculateGroupNotificationTime(groupEntry, now)
63             notificationGroupTimes[groupEntry] = whenMillis
64             if (whenMillis > now) {
65                 closestFutureTime = min(closestFutureTime, whenMillis)
66             }
67         }
68 
69         if (closestFutureTime != Long.MAX_VALUE) {
70             cancelInvalidateListRunnable =
71                 delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now)
72         }
73     }
74 
75     private fun cancelListInvalidation() {
76         cancelInvalidateListRunnable?.run()
77         cancelInvalidateListRunnable = null
78     }
79 
80     private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) {
81         notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen)
82     }
83 
84     private fun calculateGroupNotificationTime(
85         groupEntry: GroupEntry,
86         currentTimeMillis: Long
87     ): Long {
88         var pastTime = Long.MIN_VALUE
89         var futureTime = Long.MAX_VALUE
90         groupEntry.children
91             .asSequence()
92             .mapNotNull { child -> child.sbn.notification.getWhen().takeIf { it > 0 } }
93             .forEach { time ->
94                 val isInThePast = currentTimeMillis - time > 0
95                 if (isInThePast) {
96                     pastTime = max(pastTime, time)
97                 } else {
98                     futureTime = min(futureTime, time)
99                 }
100             }
101 
102         if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) {
103             return checkNotNull(groupEntry.summary).creationTime
104         }
105 
106         return if (futureTime != Long.MAX_VALUE) futureTime else pastTime
107     }
108 }
109