• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 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.stack
18 
19 import android.content.Context
20 import android.service.notification.NotificationListenerService.REASON_APP_CANCEL
21 import android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL
22 import android.service.notification.NotificationListenerService.REASON_CANCEL
23 import android.service.notification.NotificationListenerService.REASON_CANCEL_ALL
24 import android.service.notification.NotificationListenerService.REASON_CLICK
25 import android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED
26 import android.view.LayoutInflater
27 import android.view.View
28 import android.widget.LinearLayout
29 import com.android.systemui.R
30 import com.android.systemui.dagger.SysUISingleton
31 import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController
32 import com.android.systemui.statusbar.notification.NotificationEntryListener
33 import com.android.systemui.statusbar.notification.NotificationEntryManager
34 import com.android.systemui.statusbar.notification.collection.NotificationEntry
35 import com.android.systemui.statusbar.notification.row.DungeonRow
36 import com.android.systemui.util.Assert
37 import javax.inject.Inject
38 
39 /**
40  * Controller for the bottom area of NotificationStackScrollLayout. It owns swiped-away foreground
41  * service notifications and can reinstantiate them when requested.
42  */
43 @SysUISingleton
44 class ForegroundServiceSectionController @Inject constructor(
45     val entryManager: NotificationEntryManager,
46     val featureController: ForegroundServiceDismissalFeatureController
47 ) {
48     private val TAG = "FgsSectionController"
49     private var context: Context? = null
50 
51     private val entries = mutableSetOf<NotificationEntry>()
52 
53     private var entriesView: View? = null
54 
55     init {
56         if (featureController.isForegroundServiceDismissalEnabled()) {
57             entryManager.addNotificationRemoveInterceptor(this::shouldInterceptRemoval)
58 
59             entryManager.addNotificationEntryListener(object : NotificationEntryListener {
60                 override fun onPostEntryUpdated(entry: NotificationEntry) {
61                     if (entries.contains(entry)) {
62                         removeEntry(entry)
63                         addEntry(entry)
64                         update()
65                     }
66                 }
67             })
68         }
69     }
70 
71     private fun shouldInterceptRemoval(
72         key: String,
73         entry: NotificationEntry?,
74         reason: Int
75     ): Boolean {
76         Assert.isMainThread()
77         val isClearAll = reason == REASON_CANCEL_ALL
78         val isUserDismiss = reason == REASON_CANCEL || reason == REASON_CLICK
79         val isAppCancel = reason == REASON_APP_CANCEL || reason == REASON_APP_CANCEL_ALL
80         val isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED
81 
82         if (entry == null) return false
83 
84         // We only want to retain notifications that the user dismissed
85         // TODO: centralize the entry.isClearable logic and this so that it's clear when a notif is
86         // clearable
87         if (isUserDismiss && !entry.sbn.isClearable) {
88             if (!hasEntry(entry)) {
89                 addEntry(entry)
90                 update()
91             }
92             // TODO: This isn't ideal. Slightly better would at least be to have NEM update the
93             // notif list when an entry gets intercepted
94             entryManager.updateNotifications(
95                     "FgsSectionController.onNotificationRemoveRequested")
96             return true
97         } else if ((isClearAll || isSummaryCancel) && !entry.sbn.isClearable) {
98             // In the case where a FGS notification is part of a group that is cleared or a clear
99             // all, we actually want to stop its removal but also not put it into the dungeon
100             return true
101         } else if (hasEntry(entry)) {
102             removeEntry(entry)
103             update()
104             return false
105         }
106 
107         return false
108     }
109 
110     private fun removeEntry(entry: NotificationEntry) {
111         Assert.isMainThread()
112         entries.remove(entry)
113     }
114 
115     private fun addEntry(entry: NotificationEntry) {
116         Assert.isMainThread()
117         entries.add(entry)
118     }
119 
120     fun hasEntry(entry: NotificationEntry): Boolean {
121         Assert.isMainThread()
122         return entries.contains(entry)
123     }
124 
125     fun initialize(context: Context) {
126         this.context = context
127     }
128 
129     fun createView(li: LayoutInflater): View {
130         entriesView = li.inflate(R.layout.foreground_service_dungeon, null)
131         // Start out gone
132         entriesView!!.visibility = View.GONE
133         return entriesView!!
134     }
135 
136     private fun update() {
137         Assert.isMainThread()
138         if (entriesView == null) {
139             throw IllegalStateException("ForegroundServiceSectionController is trying to show " +
140                     "dismissed fgs notifications without having been initialized!")
141         }
142 
143         // TODO: these views should be recycled and not inflating on the main thread
144         (entriesView!!.findViewById(R.id.entry_list) as LinearLayout).apply {
145             removeAllViews()
146             entries.sortedBy { it.ranking.rank }.forEach { entry ->
147                 val child = LayoutInflater.from(context)
148                         .inflate(R.layout.foreground_service_dungeon_row, null) as DungeonRow
149 
150                 child.entry = entry
151                 child.setOnClickListener {
152                     removeEntry(child.entry!!)
153                     update()
154                     entry.row.unDismiss()
155                     entry.row.resetTranslation()
156                     entryManager.updateNotifications("ForegroundServiceSectionController.onClick")
157                 }
158 
159                 addView(child)
160             }
161         }
162 
163         if (entries.isEmpty()) {
164             entriesView?.visibility = View.GONE
165         } else {
166             entriesView?.visibility = View.VISIBLE
167         }
168     }
169 }
170