blob: 63f63a5093d2ab8506e35c3300d880ad09574db6 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.dreams.smartspace
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named
/**
* Controller for managing the smartspace view on the dream
*/
@SysUISingleton
class DreamSmartspaceController @Inject constructor(
private val context: Context,
private val smartspaceManager: SmartspaceManager,
private val execution: Execution,
@Main private val uiExecutor: Executor,
private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
companion object {
private const val TAG = "DreamSmartspaceCtrlr"
}
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
// A shadow copy of listeners is maintained to track whether the session should remain open.
private var listeners = mutableSetOf<SmartspaceTargetListener>()
private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
var preconditionListener = object : SmartspacePrecondition.Listener {
override fun onCriteriaChanged() {
reloadSmartspace()
}
}
init {
precondition.addListener(preconditionListener)
}
var filterListener = object : SmartspaceTargetFilter.Listener {
override fun onCriteriaChanged() {
reloadSmartspace()
}
}
init {
targetFilter?.addListener(filterListener)
}
var stateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
val view = v as SmartspaceView
// Until there is dream color matching
view.setPrimaryTextColor(Color.WHITE)
smartspaceViews.add(view)
connectSession()
view.setDozeAmount(0f)
}
override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView)
if (smartspaceViews.isEmpty()) {
disconnect()
}
}
}
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
onTargetsAvailableUnfiltered(targets)
val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
plugin?.onTargetsAvailable(filteredTargets)
}
/**
* Constructs the smartspace view and connects it to the smartspace service.
*/
fun buildAndConnectView(parent: ViewGroup): View? {
execution.assertIsMainThread()
if (!precondition.conditionsMet()) {
throw RuntimeException("Cannot build view when not enabled")
}
val view = buildView(parent)
connectSession()
return view
}
private fun buildView(parent: ViewGroup): View? {
return if (plugin != null) {
var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
.getView()
if (view !is View) {
return null
}
return view
} else {
null
}
}
private fun hasActiveSessionListeners(): Boolean {
return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
unfilteredListeners.isNotEmpty()
}
private fun connectSession() {
if (plugin == null || session != null || !hasActiveSessionListeners()) {
return
}
if (!precondition.conditionsMet()) {
return
}
val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "dream").build()
)
Log.d(TAG, "Starting smartspace session for dream")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
plugin.registerSmartspaceEventNotifier {
e ->
session?.notifySmartspaceEvent(e)
}
reloadSmartspace()
}
/**
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
*/
private fun disconnect() {
if (hasActiveSessionListeners()) return
execution.assertIsMainThread()
if (session == null) {
return
}
session?.let {
it.removeOnTargetsAvailableListener(sessionListener)
it.close()
}
session = null
plugin?.registerSmartspaceEventNotifier(null)
plugin?.onTargetsAvailable(emptyList())
Log.d(TAG, "Ending smartspace session for dream")
}
fun addListener(listener: SmartspaceTargetListener) {
execution.assertIsMainThread()
plugin?.registerListener(listener)
listeners.add(listener)
connectSession()
}
fun removeListener(listener: SmartspaceTargetListener) {
execution.assertIsMainThread()
plugin?.unregisterListener(listener)
listeners.remove(listener)
disconnect()
}
private fun reloadSmartspace() {
session?.requestSmartspaceUpdate()
}
private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
}
/**
* Adds a listener for the raw, unfiltered list of smartspace targets. This should be used
* carefully, as it doesn't filter out targets which the user may not want shown.
*/
fun addUnfilteredListener(listener: SmartspaceTargetListener) {
unfilteredListeners.add(listener)
connectSession()
}
fun removeUnfilteredListener(listener: SmartspaceTargetListener) {
unfilteredListeners.remove(listener)
disconnect()
}
}