• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log
23 import com.android.systemui.dagger.SysUISingleton
24 import com.android.systemui.dagger.qualifiers.Main
25 import com.android.systemui.plugins.BcSmartspaceDataPlugin
26 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
27 import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
28 import com.android.systemui.settings.UserTracker
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 userTracker: UserTracker,
46     private val execution: Execution,
47     @Main private val uiExecutor: Executor,
48     @Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
49     @Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
50     private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
51     @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
52 ) {
53     companion object {
54         private const val TAG = "CommunalSmartspaceCtrlr"
55     }
56 
57     private var userSmartspaceManager: SmartspaceManager? = null
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                 if (session == null && hasActiveSessionListeners()) {
69                     Log.d(TAG, "Precondition criteria changed. Attempting to connect session.")
70                     connectSession()
71                     return
72                 }
73 
74                 reloadSmartspace()
75             }
76         }
77 
78     init {
79         precondition.addListener(preconditionListener)
80     }
81 
82     var filterListener =
83         object : SmartspaceTargetFilter.Listener {
84             override fun onCriteriaChanged() {
85                 reloadSmartspace()
86             }
87         }
88 
89     init {
90         targetFilter?.addListener(filterListener)
91     }
92 
93     private val sessionListener =
94         SmartspaceSession.OnTargetsAvailableListener { targets ->
95             execution.assertIsMainThread()
96 
97             val filteredTargets =
98                 targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
99             plugin?.onTargetsAvailable(filteredTargets)
100         }
101 
102     private fun hasActiveSessionListeners(): Boolean {
103         return listeners.isNotEmpty()
104     }
105 
106     private fun connectSession() {
107         if (userSmartspaceManager == null) {
108             userSmartspaceManager =
109                 userTracker.userContext.getSystemService(SmartspaceManager::class.java)
110         }
111         if (userSmartspaceManager == null) {
112             return
113         }
114         if (plugin == null) {
115             return
116         }
117         if (session != null || !hasActiveSessionListeners()) {
118             return
119         }
120 
121         if (!precondition.conditionsMet()) {
122             return
123         }
124 
125         val newSession =
126             userSmartspaceManager?.createSmartspaceSession(
127                 SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
128             )
129         Log.d(TAG, "Starting smartspace session for communal")
130         newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
131         this.session = newSession
132 
133         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
134 
135         reloadSmartspace()
136     }
137 
138     /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
139     private fun disconnect() {
140         if (hasActiveSessionListeners()) return
141 
142         execution.assertIsMainThread()
143 
144         if (session == null) {
145             return
146         }
147 
148         session?.let {
149             it.removeOnTargetsAvailableListener(sessionListener)
150             it.close()
151         }
152 
153         session = null
154 
155         plugin?.registerSmartspaceEventNotifier(null)
156         plugin?.onTargetsAvailable(emptyList())
157         Log.d(TAG, "Ending smartspace session for communal")
158     }
159 
160     fun addListener(listener: SmartspaceTargetListener) {
161         addAndRegisterListener(listener, plugin)
162     }
163 
164     fun removeListener(listener: SmartspaceTargetListener) {
165         removeAndUnregisterListener(listener, plugin)
166     }
167 
168     private fun addAndRegisterListener(
169         listener: SmartspaceTargetListener,
170         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
171     ) {
172         execution.assertIsMainThread()
173         smartspaceDataPlugin?.registerListener(listener)
174         listeners.add(listener)
175 
176         connectSession()
177     }
178 
179     private fun removeAndUnregisterListener(
180         listener: SmartspaceTargetListener,
181         smartspaceDataPlugin: BcSmartspaceDataPlugin?,
182     ) {
183         execution.assertIsMainThread()
184         smartspaceDataPlugin?.unregisterListener(listener)
185         listeners.remove(listener)
186         disconnect()
187     }
188 
189     private fun reloadSmartspace() {
190         session?.requestSmartspaceUpdate()
191     }
192 }
193