blob: 87bdfa8e04ec3827cae34a7d1dcb91061e7118f9 [file] [log] [blame]
/*
* Copyright (C) 2019 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.controls.controller
import android.content.ComponentName
import android.content.Context
import android.os.IBinder
import android.os.UserHandle
import android.service.controls.Control
import android.service.controls.IControlsActionCallback
import android.service.controls.IControlsLoadCallback
import android.service.controls.IControlsSubscriber
import android.service.controls.IControlsSubscription
import android.service.controls.actions.ControlAction
import android.util.ArrayMap
import android.util.Log
import com.android.internal.annotations.GuardedBy
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
@VisibleForTesting
open class ControlsBindingControllerImpl @Inject constructor(
private val context: Context,
@Background private val backgroundExecutor: DelayableExecutor,
private val lazyController: Lazy<ControlsController>
) : ControlsBindingController {
companion object {
private const val TAG = "ControlsBindingControllerImpl"
}
private val refreshing = AtomicBoolean(false)
private var currentUser = context.user
override val currentUserId: Int
get() = currentUser.identifier
@GuardedBy("componentMap")
private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
ArrayMap<IBinder, ControlsProviderLifecycleManager>()
@GuardedBy("componentMap")
private val componentMap: MutableMap<Key, ControlsProviderLifecycleManager> =
ArrayMap<Key, ControlsProviderLifecycleManager>()
private val loadCallbackService = object : IControlsLoadCallback.Stub() {
override fun accept(token: IBinder, controls: MutableList<Control>) {
backgroundExecutor.execute(OnLoadRunnable(token, controls))
}
}
private val actionCallbackService = object : IControlsActionCallback.Stub() {
override fun accept(
token: IBinder,
controlId: String,
@ControlAction.ResponseResult response: Int
) {
backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
}
}
private val subscriberService = object : IControlsSubscriber.Stub() {
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
backgroundExecutor.execute(OnSubscribeRunnable(token, subs))
}
override fun onNext(token: IBinder, c: Control) {
if (!refreshing.get()) {
Log.d(TAG, "Refresh outside of window for token:$token")
} else {
backgroundExecutor.execute(OnNextRunnable(token, c))
}
}
override fun onError(token: IBinder, s: String) {
backgroundExecutor.execute(OnErrorRunnable(token, s))
}
override fun onComplete(token: IBinder) {
backgroundExecutor.execute(OnCompleteRunnable(token))
}
}
@VisibleForTesting
internal open fun createProviderManager(component: ComponentName):
ControlsProviderLifecycleManager {
return ControlsProviderLifecycleManager(
context,
backgroundExecutor,
loadCallbackService,
actionCallbackService,
subscriberService,
currentUser,
component
)
}
private fun retrieveLifecycleManager(component: ComponentName):
ControlsProviderLifecycleManager {
synchronized(componentMap) {
val provider = componentMap.getOrPut(Key(component, currentUser)) {
createProviderManager(component)
}
tokenMap.putIfAbsent(provider.token, provider)
return provider
}
}
override fun bindAndLoad(
component: ComponentName,
callback: ControlsBindingController.LoadCallback
) {
val provider = retrieveLifecycleManager(component)
provider.maybeBindAndLoad(callback)
}
override fun subscribe(controls: List<ControlInfo>) {
val controlsByComponentName = controls.groupBy { it.component }
if (refreshing.compareAndSet(false, true)) {
controlsByComponentName.forEach {
val provider = retrieveLifecycleManager(it.key)
backgroundExecutor.execute {
provider.maybeBindAndSubscribe(it.value.map { it.controlId })
}
}
}
// Unbind unneeded providers
val providersWithFavorites = controlsByComponentName.keys
synchronized(componentMap) {
componentMap.forEach {
if (it.key.component !in providersWithFavorites) {
backgroundExecutor.execute { it.value.unbindService() }
}
}
}
}
override fun unsubscribe() {
if (refreshing.compareAndSet(true, false)) {
val providers = synchronized(componentMap) {
componentMap.values.toList()
}
providers.forEach {
backgroundExecutor.execute { it.unsubscribe() }
}
}
}
override fun action(controlInfo: ControlInfo, action: ControlAction) {
val provider = retrieveLifecycleManager(controlInfo.component)
provider.maybeBindAndSendAction(controlInfo.controlId, action)
}
override fun bindServices(components: List<ComponentName>) {
components.forEach {
val provider = retrieveLifecycleManager(it)
backgroundExecutor.execute { provider.bindService() }
}
}
override fun changeUser(newUser: UserHandle) {
if (newUser == currentUser) return
synchronized(componentMap) {
unbindAllProvidersLocked() // unbind all providers from the old user
}
refreshing.set(false)
currentUser = newUser
}
private fun unbindAllProvidersLocked() {
componentMap.values.forEach {
if (it.user == currentUser) {
it.unbindService()
}
}
}
override fun toString(): String {
return StringBuilder(" ControlsBindingController:\n").apply {
append(" refreshing=${refreshing.get()}\n")
append(" currentUser=$currentUser\n")
append(" Providers:\n")
synchronized(componentMap) {
componentMap.values.forEach {
append(" $it\n")
}
}
}.toString()
}
private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
protected val provider: ControlsProviderLifecycleManager? =
synchronized(componentMap) {
tokenMap.get(token)
}
}
private inner class OnLoadRunnable(
token: IBinder,
val list: List<Control>
) : CallbackRunnable(token) {
override fun run() {
if (provider == null) {
Log.e(TAG, "No provider found for token:$token")
return
}
if (provider.user != currentUser) {
Log.e(TAG, "User ${provider.user} is not current user")
return
}
synchronized(componentMap) {
if (token !in tokenMap.keys) {
Log.e(TAG, "Provider for token:$token does not exist anymore")
return
}
}
provider.lastLoadCallback?.accept(list) ?: run {
Log.w(TAG, "Null callback")
}
provider.unbindService()
}
}
private inner class OnNextRunnable(
token: IBinder,
val control: Control
) : CallbackRunnable(token) {
override fun run() {
if (!refreshing.get()) {
Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
}
if (provider?.user != currentUser) {
Log.e(TAG, "User ${provider?.user} is not current user")
return
}
provider?.let {
lazyController.get().refreshStatus(it.componentName, control)
}
}
}
private inner class OnSubscribeRunnable(
token: IBinder,
val subscription: IControlsSubscription
) : CallbackRunnable(token) {
override fun run() {
if (!refreshing.get()) {
Log.d(TAG, "onRefresh outside of window from '${provider?.componentName}'")
}
provider?.let {
it.startSubscription(subscription)
}
}
}
private inner class OnCompleteRunnable(
token: IBinder
) : CallbackRunnable(token) {
override fun run() {
provider?.let {
Log.i(TAG, "onComplete receive from '${provider.componentName}'")
}
}
}
private inner class OnErrorRunnable(
token: IBinder,
val error: String
) : CallbackRunnable(token) {
override fun run() {
provider?.let {
Log.e(TAG, "onError receive from '${provider.componentName}': $error")
}
}
}
private inner class OnActionResponseRunnable(
token: IBinder,
val controlId: String,
@ControlAction.ResponseResult val response: Int
) : CallbackRunnable(token) {
override fun run() {
if (provider?.user != currentUser) {
Log.e(TAG, "User ${provider?.user} is not current user")
return
}
provider?.let {
lazyController.get().onActionResponse(it.componentName, controlId, response)
}
}
}
}
private data class Key(val component: ComponentName, val user: UserHandle)