| /* |
| * 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.app.ActivityManager |
| 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.IControlsSubscriber |
| import android.service.controls.IControlsSubscription |
| import android.service.controls.actions.ControlAction |
| import android.util.Log |
| 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 const val MAX_CONTROLS_REQUEST = 100000L |
| private const val SUGGESTED_CONTROLS_REQUEST = 6L |
| } |
| |
| private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) |
| |
| override val currentUserId: Int |
| get() = currentUser.identifier |
| |
| private var currentProvider: ControlsProviderLifecycleManager? = null |
| |
| /* |
| * Will track any active subscriber for subscribe/unsubscribe requests coming into |
| * this controller. Only one can be active at any time |
| */ |
| private var statefulControlSubscriber: StatefulControlSubscriber? = null |
| |
| /* |
| * Will track any active load subscriber. Only one can be active at any time. |
| */ |
| private var loadSubscriber: LoadSubscriber? = null |
| |
| private val actionCallbackService = object : IControlsActionCallback.Stub() { |
| override fun accept( |
| token: IBinder, |
| controlId: String, |
| @ControlAction.ResponseResult response: Int |
| ) { |
| backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response)) |
| } |
| } |
| |
| @VisibleForTesting |
| internal open fun createProviderManager(component: ComponentName): |
| ControlsProviderLifecycleManager { |
| return ControlsProviderLifecycleManager( |
| context, |
| backgroundExecutor, |
| actionCallbackService, |
| currentUser, |
| component |
| ) |
| } |
| |
| private fun retrieveLifecycleManager(component: ComponentName): |
| ControlsProviderLifecycleManager { |
| if (currentProvider != null && currentProvider?.componentName != component) { |
| unbind() |
| } |
| |
| val provider = currentProvider ?: createProviderManager(component) |
| currentProvider = provider |
| |
| return provider |
| } |
| |
| override fun bindAndLoad( |
| component: ComponentName, |
| callback: ControlsBindingController.LoadCallback |
| ): Runnable { |
| loadSubscriber?.loadCancel() |
| |
| val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST) |
| loadSubscriber = ls |
| |
| retrieveLifecycleManager(component).maybeBindAndLoad(ls) |
| return ls.loadCancel() |
| } |
| |
| override fun bindAndLoadSuggested( |
| component: ComponentName, |
| callback: ControlsBindingController.LoadCallback |
| ) { |
| loadSubscriber?.loadCancel() |
| val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST) |
| loadSubscriber = ls |
| |
| retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls) |
| } |
| |
| override fun subscribe(structureInfo: StructureInfo) { |
| // make sure this has happened. only allow one active subscription |
| unsubscribe() |
| |
| val provider = retrieveLifecycleManager(structureInfo.componentName) |
| val scs = StatefulControlSubscriber( |
| lazyController.get(), |
| provider, |
| backgroundExecutor, |
| MAX_CONTROLS_REQUEST |
| ) |
| statefulControlSubscriber = scs |
| provider.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId }, scs) |
| } |
| |
| override fun unsubscribe() { |
| statefulControlSubscriber?.cancel() |
| statefulControlSubscriber = null |
| } |
| |
| override fun action( |
| componentName: ComponentName, |
| controlInfo: ControlInfo, |
| action: ControlAction |
| ) { |
| if (statefulControlSubscriber == null) { |
| Log.w(TAG, "No actions can occur outside of an active subscription. Ignoring.") |
| } else { |
| retrieveLifecycleManager(componentName) |
| .maybeBindAndSendAction(controlInfo.controlId, action) |
| } |
| } |
| |
| override fun bindService(component: ComponentName) { |
| retrieveLifecycleManager(component).bindService() |
| } |
| |
| override fun changeUser(newUser: UserHandle) { |
| if (newUser == currentUser) return |
| |
| unbind() |
| currentUser = newUser |
| } |
| |
| private fun unbind() { |
| unsubscribe() |
| |
| loadSubscriber?.loadCancel() |
| loadSubscriber = null |
| |
| currentProvider?.unbindService() |
| currentProvider = null |
| } |
| |
| override fun onComponentRemoved(componentName: ComponentName) { |
| backgroundExecutor.execute { |
| currentProvider?.let { |
| if (it.componentName == componentName) { |
| unbind() |
| } |
| } |
| } |
| } |
| |
| override fun toString(): String { |
| return StringBuilder(" ControlsBindingController:\n").apply { |
| append(" currentUser=$currentUser\n") |
| append(" StatefulControlSubscriber=$statefulControlSubscriber") |
| append(" Providers=$currentProvider\n") |
| }.toString() |
| } |
| |
| private abstract inner class CallbackRunnable(val token: IBinder) : Runnable { |
| protected val provider: ControlsProviderLifecycleManager? = currentProvider |
| |
| override fun run() { |
| if (provider == null) { |
| Log.e(TAG, "No current provider set") |
| return |
| } |
| if (provider.user != currentUser) { |
| Log.e(TAG, "User ${provider.user} is not current user") |
| return |
| } |
| if (token != provider.token) { |
| Log.e(TAG, "Provider for token:$token does not exist anymore") |
| return |
| } |
| |
| doRun() |
| } |
| |
| abstract fun doRun() |
| } |
| |
| private inner class OnLoadRunnable( |
| token: IBinder, |
| val list: List<Control>, |
| val callback: ControlsBindingController.LoadCallback |
| ) : CallbackRunnable(token) { |
| override fun doRun() { |
| Log.d(TAG, "LoadSubscription: Complete and loading controls") |
| callback.accept(list) |
| } |
| } |
| |
| private inner class OnCancelAndLoadRunnable( |
| token: IBinder, |
| val list: List<Control>, |
| val subscription: IControlsSubscription, |
| val callback: ControlsBindingController.LoadCallback |
| ) : CallbackRunnable(token) { |
| override fun doRun() { |
| Log.d(TAG, "LoadSubscription: Canceling and loading controls") |
| provider?.cancelSubscription(subscription) |
| callback.accept(list) |
| } |
| } |
| |
| private inner class OnSubscribeRunnable( |
| token: IBinder, |
| val subscription: IControlsSubscription, |
| val requestLimit: Long |
| ) : CallbackRunnable(token) { |
| override fun doRun() { |
| Log.d(TAG, "LoadSubscription: Starting subscription") |
| provider?.startSubscription(subscription, requestLimit) |
| } |
| } |
| |
| private inner class OnActionResponseRunnable( |
| token: IBinder, |
| val controlId: String, |
| @ControlAction.ResponseResult val response: Int |
| ) : CallbackRunnable(token) { |
| override fun doRun() { |
| provider?.let { |
| lazyController.get().onActionResponse(it.componentName, controlId, response) |
| } |
| } |
| } |
| |
| private inner class OnLoadErrorRunnable( |
| token: IBinder, |
| val error: String, |
| val callback: ControlsBindingController.LoadCallback |
| ) : CallbackRunnable(token) { |
| override fun doRun() { |
| callback.error(error) |
| provider?.let { |
| Log.e(TAG, "onError receive from '${it.componentName}': $error") |
| } |
| } |
| } |
| |
| private inner class LoadSubscriber( |
| val callback: ControlsBindingController.LoadCallback, |
| val requestLimit: Long |
| ) : IControlsSubscriber.Stub() { |
| val loadedControls = ArrayList<Control>() |
| private var isTerminated = AtomicBoolean(false) |
| private var _loadCancelInternal: (() -> Unit)? = null |
| private lateinit var subscription: IControlsSubscription |
| |
| /** |
| * Potentially cancel a subscriber. The subscriber may also have terminated, in which case |
| * the request is ignored. |
| */ |
| fun loadCancel() = Runnable { |
| _loadCancelInternal?.let { |
| Log.d(TAG, "Canceling loadSubscribtion") |
| it.invoke() |
| } |
| } |
| |
| override fun onSubscribe(token: IBinder, subs: IControlsSubscription) { |
| subscription = subs |
| _loadCancelInternal = { currentProvider?.cancelSubscription(subscription) } |
| backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit)) |
| } |
| |
| override fun onNext(token: IBinder, c: Control) { |
| backgroundExecutor.execute { |
| if (isTerminated.get()) return@execute |
| |
| loadedControls.add(c) |
| |
| // Once we have reached our requestLimit, send a request to cancel, and immediately |
| // load the results. Calls to onError() and onComplete() are not required after |
| // cancel. |
| if (loadedControls.size >= requestLimit) { |
| maybeTerminateAndRun( |
| OnCancelAndLoadRunnable(token, loadedControls, subscription, callback) |
| ) |
| } |
| } |
| } |
| |
| override fun onError(token: IBinder, s: String) { |
| maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback)) |
| } |
| |
| override fun onComplete(token: IBinder) { |
| maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback)) |
| } |
| |
| private fun maybeTerminateAndRun(postTerminateFn: Runnable) { |
| if (isTerminated.get()) return |
| |
| _loadCancelInternal = {} |
| currentProvider?.cancelLoadTimeout() |
| |
| backgroundExecutor.execute { |
| isTerminated.compareAndSet(false, true) |
| postTerminateFn.run() |
| } |
| } |
| } |
| } |
| |
| private data class Key(val component: ComponentName, val user: UserHandle) |