1 /*
2  * Copyright (C) 2019 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.controls.controller
18 
19 import android.content.ComponentName
20 import android.content.Context
21 import android.os.IBinder
22 import android.os.UserHandle
23 import android.service.controls.Control
24 import android.service.controls.IControlsActionCallback
25 import android.service.controls.IControlsSubscriber
26 import android.service.controls.IControlsSubscription
27 import android.service.controls.actions.ControlAction
28 import android.util.Log
29 import com.android.internal.annotations.VisibleForTesting
30 import com.android.systemui.dagger.SysUISingleton
31 import com.android.systemui.dagger.qualifiers.Background
32 import com.android.systemui.settings.UserTracker
33 import com.android.systemui.util.concurrency.DelayableExecutor
34 import dagger.Lazy
35 import java.util.concurrent.atomic.AtomicBoolean
36 import javax.inject.Inject
37 
38 @SysUISingleton
39 @VisibleForTesting
40 open class ControlsBindingControllerImpl @Inject constructor(
41     private val context: Context,
42     @Background private val backgroundExecutor: DelayableExecutor,
43     private val lazyController: Lazy<ControlsController>,
44     private val packageUpdateMonitorFactory: PackageUpdateMonitor.Factory,
45     userTracker: UserTracker,
46 ) : ControlsBindingController {
47 
48     companion object {
49         private const val TAG = "ControlsBindingControllerImpl"
50         private const val MAX_CONTROLS_REQUEST = 100000L
51         private const val SUGGESTED_STRUCTURES = 6L
52         private const val SUGGESTED_CONTROLS_REQUEST =
53             ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE * SUGGESTED_STRUCTURES
54 
55         private val emptyCallback = object : ControlsBindingController.LoadCallback {
acceptnull56             override fun accept(controls: List<Control>) {}
errornull57             override fun error(message: String) {}
58         }
59     }
60 
61     private var currentUser = userTracker.userHandle
62 
63     override val currentUserId: Int
64         get() = currentUser.identifier
65 
66     private var currentProvider: ControlsProviderLifecycleManager? = null
67 
68     /*
69      * Will track any active subscriber for subscribe/unsubscribe requests coming into
70      * this controller. Only one can be active at any time
71      */
72     private var statefulControlSubscriber: StatefulControlSubscriber? = null
73 
74     /*
75      * Will track any active load subscriber. Only one can be active at any time.
76      */
77     private var loadSubscriber: LoadSubscriber? = null
78 
79     private val actionCallbackService = object : IControlsActionCallback.Stub() {
acceptnull80         override fun accept(
81             token: IBinder,
82             controlId: String,
83             @ControlAction.ResponseResult response: Int
84         ) {
85             backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
86         }
87     }
88 
89     @VisibleForTesting
createProviderManagernull90     internal open fun createProviderManager(component: ComponentName):
91             ControlsProviderLifecycleManager {
92         return ControlsProviderLifecycleManager(
93                 context,
94                 backgroundExecutor,
95                 actionCallbackService,
96                 currentUser,
97                 component,
98                 packageUpdateMonitorFactory
99         )
100     }
101 
retrieveLifecycleManagernull102     private fun retrieveLifecycleManager(component: ComponentName):
103             ControlsProviderLifecycleManager {
104         if (currentProvider != null && currentProvider?.componentName != component) {
105             unbind()
106         }
107 
108         val provider = currentProvider ?: createProviderManager(component)
109         currentProvider = provider
110 
111         return provider
112     }
113 
bindAndLoadnull114     override fun bindAndLoad(
115         component: ComponentName,
116         callback: ControlsBindingController.LoadCallback
117     ): Runnable {
118         loadSubscriber?.loadCancel()
119 
120         val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
121         loadSubscriber = ls
122 
123         retrieveLifecycleManager(component).maybeBindAndLoad(ls)
124         return ls.loadCancel()
125     }
126 
bindAndLoadSuggestednull127     override fun bindAndLoadSuggested(
128         component: ComponentName,
129         callback: ControlsBindingController.LoadCallback
130     ) {
131         loadSubscriber?.loadCancel()
132         val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
133         loadSubscriber = ls
134 
135         retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls)
136     }
137 
subscribenull138     override fun subscribe(structureInfo: StructureInfo) {
139         // make sure this has happened. only allow one active subscription
140         unsubscribe()
141 
142         val provider = retrieveLifecycleManager(structureInfo.componentName)
143         val scs = StatefulControlSubscriber(
144             lazyController.get(),
145             provider,
146             backgroundExecutor,
147             MAX_CONTROLS_REQUEST
148         )
149         statefulControlSubscriber = scs
150         provider.maybeBindAndSubscribe(structureInfo.controls.map { it.controlId }, scs)
151     }
152 
unsubscribenull153     override fun unsubscribe() {
154         statefulControlSubscriber?.cancel()
155         statefulControlSubscriber = null
156     }
157 
actionnull158     override fun action(
159         componentName: ComponentName,
160         controlInfo: ControlInfo,
161         action: ControlAction
162     ) {
163         if (statefulControlSubscriber == null) {
164             Log.w(TAG, "No actions can occur outside of an active subscription. Ignoring.")
165         } else {
166             retrieveLifecycleManager(componentName)
167                 .maybeBindAndSendAction(controlInfo.controlId, action)
168         }
169     }
170 
bindServicenull171     override fun bindService(component: ComponentName) {
172         retrieveLifecycleManager(component).bindService()
173     }
174 
bindServiceForPanelnull175     override fun bindServiceForPanel(component: ComponentName) {
176         retrieveLifecycleManager(component).bindServiceForPanel()
177     }
178 
changeUsernull179     override fun changeUser(newUser: UserHandle) {
180         if (newUser == currentUser) return
181 
182         unbind()
183         currentUser = newUser
184     }
185 
unbindnull186     private fun unbind() {
187         unsubscribe()
188 
189         loadSubscriber?.loadCancel()
190         loadSubscriber = null
191 
192         currentProvider?.unbindService()
193         currentProvider = null
194     }
195 
onComponentRemovednull196     override fun onComponentRemoved(componentName: ComponentName) {
197         backgroundExecutor.execute {
198             currentProvider?.let {
199                 if (it.componentName == componentName) {
200                     unbind()
201                 }
202             }
203         }
204     }
205 
toStringnull206     override fun toString(): String {
207         return StringBuilder("  ControlsBindingController:\n").apply {
208             append("    currentUser=$currentUser\n")
209             append("    StatefulControlSubscriber=$statefulControlSubscriber")
210             append("    Providers=$currentProvider\n")
211         }.toString()
212     }
213 
214     private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
215         protected val provider: ControlsProviderLifecycleManager? = currentProvider
216 
runnull217         override fun run() {
218             if (provider == null) {
219                 Log.e(TAG, "No current provider set")
220                 return
221             }
222             if (provider.user != currentUser) {
223                 Log.e(TAG, "User ${provider.user} is not current user")
224                 return
225             }
226             if (token != provider.token) {
227                 Log.e(TAG, "Provider for token:$token does not exist anymore")
228                 return
229             }
230 
231             doRun()
232         }
233 
doRunnull234         abstract fun doRun()
235     }
236 
237     private inner class OnLoadRunnable(
238         token: IBinder,
239         val list: List<Control>,
240         val callback: ControlsBindingController.LoadCallback
241     ) : CallbackRunnable(token) {
242         override fun doRun() {
243             Log.d(TAG, "LoadSubscription: Complete and loading controls")
244             callback.accept(list)
245         }
246     }
247 
248     private inner class OnCancelAndLoadRunnable(
249         token: IBinder,
250         val list: List<Control>,
251         val subscription: IControlsSubscription,
252         val callback: ControlsBindingController.LoadCallback
253     ) : CallbackRunnable(token) {
doRunnull254         override fun doRun() {
255             Log.d(TAG, "LoadSubscription: Canceling and loading controls")
256             provider?.cancelSubscription(subscription)
257             callback.accept(list)
258         }
259     }
260 
261     private inner class OnSubscribeRunnable(
262         token: IBinder,
263         val subscription: IControlsSubscription,
264         val requestLimit: Long
265     ) : CallbackRunnable(token) {
doRunnull266         override fun doRun() {
267             Log.d(TAG, "LoadSubscription: Starting subscription")
268             provider?.startSubscription(subscription, requestLimit)
269         }
270     }
271 
272     private inner class OnActionResponseRunnable(
273         token: IBinder,
274         val controlId: String,
275         @ControlAction.ResponseResult val response: Int
276     ) : CallbackRunnable(token) {
doRunnull277         override fun doRun() {
278             provider?.let {
279                 lazyController.get().onActionResponse(it.componentName, controlId, response)
280             }
281         }
282     }
283 
284     private inner class OnLoadErrorRunnable(
285         token: IBinder,
286         val error: String,
287         val callback: ControlsBindingController.LoadCallback
288     ) : CallbackRunnable(token) {
doRunnull289         override fun doRun() {
290             callback.error(error)
291             provider?.let {
292                 Log.e(TAG, "onError receive from '${it.componentName}': $error")
293             }
294         }
295     }
296 
297     private inner class LoadSubscriber(
298         var callback: ControlsBindingController.LoadCallback,
299         val requestLimit: Long
300     ) : IControlsSubscriber.Stub() {
301         val loadedControls = ArrayList<Control>()
302         private var isTerminated = AtomicBoolean(false)
303         private var _loadCancelInternal: (() -> Unit)? = null
304         private lateinit var subscription: IControlsSubscription
305 
306         /**
307          * Potentially cancel a subscriber. The subscriber may also have terminated, in which case
308          * the request is ignored.
309          */
<lambda>null310         fun loadCancel() = Runnable {
311             _loadCancelInternal?.let {
312                 Log.d(TAG, "Canceling loadSubscribtion")
313                 it.invoke()
314             }
315             callback.error("Load cancelled")
316         }
317 
onSubscribenull318         override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
319             subscription = subs
320             _loadCancelInternal = { currentProvider?.cancelSubscription(subscription) }
321             backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit))
322         }
323 
onNextnull324         override fun onNext(token: IBinder, c: Control) {
325             backgroundExecutor.execute {
326                 if (isTerminated.get()) return@execute
327 
328                 loadedControls.add(c)
329 
330                 // Once we have reached our requestLimit, send a request to cancel, and immediately
331                 // load the results. Calls to onError() and onComplete() are not required after
332                 // cancel.
333                 if (loadedControls.size >= requestLimit) {
334                     maybeTerminateAndRun(
335                         OnCancelAndLoadRunnable(token, loadedControls, subscription, callback)
336                     )
337                 }
338             }
339         }
340 
onErrornull341         override fun onError(token: IBinder, s: String) {
342             maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback))
343         }
344 
onCompletenull345         override fun onComplete(token: IBinder) {
346             maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback))
347         }
348 
maybeTerminateAndRunnull349         private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
350             if (isTerminated.get()) return
351 
352             _loadCancelInternal = {}
353 
354             // Reassign the callback to clear references to other areas of code. Binders such as
355             // this may not be GC'd right away, so do not hold onto these references.
356             callback = emptyCallback
357             currentProvider?.cancelLoadTimeout()
358 
359             backgroundExecutor.execute {
360                 isTerminated.compareAndSet(false, true)
361                 postTerminateFn.run()
362             }
363         }
364     }
365 }
366 
367 private data class Key(val component: ComponentName, val user: UserHandle)
368