1 /*
2  * Copyright (C) 2021 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.ArraySet
19 import com.android.systemui.Dumpable
20 import com.android.systemui.dump.DumpManager
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.NotificationEntry
24 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
25 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
26 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
27 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener
28 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager
29 import com.android.systemui.statusbar.notification.row.NotificationGuts
30 import com.android.systemui.statusbar.notification.row.NotificationGutsManager
31 import java.io.FileDescriptor
32 import java.io.PrintWriter
33 import javax.inject.Inject
34 
35 private const val TAG = "GutsCoordinator"
36 
37 /**
38  * Coordinates the guts displayed by the [NotificationGutsManager] with the pipeline.
39  * Specifically, this just adds the lifetime extension necessary to keep guts from disappearing.
40  */
41 @CoordinatorScope
42 class GutsCoordinator @Inject constructor(
43     private val notifGutsViewManager: NotifGutsViewManager,
44     private val logger: GutsCoordinatorLogger,
45     dumpManager: DumpManager
46 ) : Coordinator, Dumpable {
47 
48     /** Keys of any Notifications for which we've been told the guts are open  */
49     private val notifsWithOpenGuts = ArraySet<String>()
50 
51     /** Keys of any Notifications we've extended the lifetime for, based on guts  */
52     private val notifsExtendingLifetime = ArraySet<String>()
53 
54     /** Callback for ending lifetime extension  */
55     private var onEndLifetimeExtensionCallback: OnEndLifetimeExtensionCallback? = null
56 
57     init {
58         dumpManager.registerDumpable(TAG, this)
59     }
60 
61     override fun attach(pipeline: NotifPipeline) {
62         notifGutsViewManager.setGutsListener(mGutsListener)
63         pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
64     }
65 
66     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
67         pw.println("  notifsWithOpenGuts: ${notifsWithOpenGuts.size}")
68         for (key in notifsWithOpenGuts) {
69             pw.println("   * $key")
70         }
71         pw.println("  notifsExtendingLifetime: ${notifsExtendingLifetime.size}")
72         for (key in notifsExtendingLifetime) {
73             pw.println("   * $key")
74         }
75         pw.println("  onEndLifetimeExtensionCallback: $onEndLifetimeExtensionCallback")
76     }
77 
78     private val mLifetimeExtender: NotifLifetimeExtender = object : NotifLifetimeExtender {
79         override fun getName(): String {
80             return TAG
81         }
82 
83         override fun setCallback(callback: OnEndLifetimeExtensionCallback) {
84             onEndLifetimeExtensionCallback = callback
85         }
86 
87         override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean {
88             val isShowingGuts = isCurrentlyShowingGuts(entry)
89             if (isShowingGuts) {
90                 notifsExtendingLifetime.add(entry.key)
91             }
92             return isShowingGuts
93         }
94 
95         override fun cancelLifetimeExtension(entry: NotificationEntry) {
96             notifsExtendingLifetime.remove(entry.key)
97         }
98     }
99 
100     private val mGutsListener: NotifGutsViewListener = object : NotifGutsViewListener {
101         override fun onGutsOpen(entry: NotificationEntry, guts: NotificationGuts) {
102             logger.logGutsOpened(entry.key, guts)
103             if (guts.isLeavebehind) {
104                 // leave-behind guts should not extend the lifetime of the notification
105                 closeGutsAndEndLifetimeExtension(entry)
106             } else {
107                 notifsWithOpenGuts.add(entry.key)
108             }
109         }
110 
111         override fun onGutsClose(entry: NotificationEntry) {
112             logger.logGutsClosed(entry.key)
113             closeGutsAndEndLifetimeExtension(entry)
114         }
115     }
116 
117     private fun isCurrentlyShowingGuts(entry: ListEntry) =
118             notifsWithOpenGuts.contains(entry.key)
119 
120     private fun closeGutsAndEndLifetimeExtension(entry: NotificationEntry) {
121         notifsWithOpenGuts.remove(entry.key)
122         if (notifsExtendingLifetime.remove(entry.key)) {
123             onEndLifetimeExtensionCallback?.onEndLifetimeExtension(mLifetimeExtender, entry)
124         }
125     }
126 }
127