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