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 
17 package com.android.systemui.communal.smartspace
18 
19 import android.app.smartspace.SmartspaceConfig
20 import android.app.smartspace.SmartspaceManager
21 import android.app.smartspace.SmartspaceSession
22 import android.content.Context
23 import android.util.Log
24 import com.android.systemui.dagger.SysUISingleton
25 import com.android.systemui.dagger.qualifiers.Main
26 import com.android.systemui.plugins.BcSmartspaceDataPlugin
27 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
28 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
29 import com.android.systemui.smartspace.SmartspacePrecondition
30 import com.android.systemui.smartspace.SmartspaceTargetFilter
31 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
32 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_PRECONDITION
33 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.LOCKSCREEN_SMARTSPACE_TARGET_FILTER
34 import com.android.systemui.util.concurrency.Execution
35 import java.util.Optional
36 import java.util.concurrent.Executor
37 import javax.inject.Inject
38 import javax.inject.Named
39 
40 /** Controller for managing the smartspace view on the glanceable hub */
41 @SysUISingleton
42 class CommunalSmartspaceController
43 @Inject
44 constructor(
45     private val context: Context,
46     private val smartspaceManager: SmartspaceManager?,
47     private val execution: Execution,
48     @Main private val uiExecutor: Executor,
49     @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
50     @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
51     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
52     @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
53 ) {
54     companion object {
55         private const val TAG = "CommunalSmartspaceCtrlr"
56     }
57 
58     private var session: SmartspaceSession? = null
59     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
60     private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
61 
62     // A shadow copy of listeners is maintained to track whether the session should remain open.
63     private var listeners = mutableSetOf<SmartspaceTargetListener>()
64 
65     var preconditionListener =
66         object : SmartspacePrecondition.Listener {
67             override fun onCriteriaChanged() {
68                 reloadSmartspace()
69             }
70         }
71 
72     init {
73         precondition.addListener(preconditionListener)
74     }
75 
76     var filterListener =
77         object : SmartspaceTargetFilter.Listener {
78             override fun onCriteriaChanged() {
79                 reloadSmartspace()
80             }
81         }
82 
83     init {
84         targetFilter?.addListener(filterListener)
85     }
86 
87     private val sessionListener =
88         SmartspaceSession.OnTargetsAvailableListener { targets ->
89             execution.assertIsMainThread()
90 
91             val filteredTargets =
92                 targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
93             plugin?.onTargetsAvailable(filteredTargets)
94         }
95 
96     private fun hasActiveSessionListeners(): Boolean {
97         return listeners.isNotEmpty()
98     }
99 
100     private fun connectSession() {
101         if (smartspaceManager == null) {
102             return
103         }
104         if (plugin == null) {
105             return
106         }
107         if (session != null || !hasActiveSessionListeners()) {
108             return
109         }
110 
111         if (!precondition.conditionsMet()) {
112             return
113         }
114 
115         val newSession =
116             smartspaceManager.createSmartspaceSession(
117                 SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
118             )
119         Log.d(TAG, "Starting smartspace session for communal")
120         newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
121         this.session = newSession
122 
123         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
124 
125         reloadSmartspace()
126     }
127 
128     /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
129     private fun disconnect() {
130         if (hasActiveSessionListeners()) return
131 
132         execution.assertIsMainThread()
133 
134         if (session == null) {
135             return
136         }
137 
138         session?.let {
139             it.removeOnTargetsAvailableListener(sessionListener)
140             it.close()
141         }
142 
143         session = null
144 
145         plugin?.registerSmartspaceEventNotifier(null)
146         plugin?.onTargetsAvailable(emptyList())
147         Log.d(TAG, "Ending smartspace session for communal")
148     }
149 
150     fun addListener(listener: SmartspaceTargetListener) {
151         addAndRegisterListener(listener, plugin)
152     }
153 
154     fun removeListener(listener: SmartspaceTargetListener) {
155         removeAndUnregisterListener(listener, plugin)
156     }
157 
158     private fun addAndRegisterListener(
159         listener: SmartspaceTargetListener,
160         smartspaceDataPlugin: BcSmartspaceDataPlugin?
161     ) {
162         execution.assertIsMainThread()
163         smartspaceDataPlugin?.registerListener(listener)
164         listeners.add(listener)
165 
166         connectSession()
167     }
168 
169     private fun removeAndUnregisterListener(
170         listener: SmartspaceTargetListener,
171         smartspaceDataPlugin: BcSmartspaceDataPlugin?
172     ) {
173         execution.assertIsMainThread()
174         smartspaceDataPlugin?.unregisterListener(listener)
175         listeners.remove(listener)
176         disconnect()
177     }
178 
179     private fun reloadSmartspace() {
180         session?.requestSmartspaceUpdate()
181     }
182 }
183