• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.content.Intent
22 import android.content.ServiceConnection
23 import android.os.Binder
24 import android.os.Bundle
25 import android.os.IBinder
26 import android.os.RemoteException
27 import android.os.UserHandle
28 import android.service.controls.ControlsProviderService
29 import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
30 import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
31 import android.service.controls.IControlsActionCallback
32 import android.service.controls.IControlsProvider
33 import android.service.controls.IControlsSubscriber
34 import android.service.controls.IControlsSubscription
35 import android.service.controls.actions.ControlAction
36 import android.util.ArraySet
37 import android.util.Log
38 import com.android.internal.annotations.GuardedBy
39 import com.android.systemui.util.concurrency.DelayableExecutor
40 import java.util.concurrent.TimeUnit
41 
42 /**
43  * Manager for the lifecycle of the connection to a given [ControlsProviderService].
44  *
45  * This class handles binding and unbinding and requests to the service. The class will queue
46  * requests until the service is connected and dispatch them then.
47  *
48  * @property context A SystemUI context for binding to the services
49  * @property executor A delayable executor for posting timeouts
50  * @property actionCallbackService a callback interface to hand the remote service for sending
51  *                                 action responses
52  * @property subscriberService an "subscriber" interface for requesting and accepting updates for
53  *                             controls from the service.
54  * @property user the user for whose this service should be bound.
55  * @property componentName the name of the component for the service.
56  */
57 class ControlsProviderLifecycleManager(
58     private val context: Context,
59     private val executor: DelayableExecutor,
60     private val actionCallbackService: IControlsActionCallback.Stub,
61     val user: UserHandle,
62     val componentName: ComponentName
63 ) : IBinder.DeathRecipient {
64 
65     val token: IBinder = Binder()
66     private var requiresBound = false
67     @GuardedBy("queuedServiceMethods")
68     private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
69     private var wrapper: ServiceWrapper? = null
70     private var bindTryCount = 0
71     private val TAG = javaClass.simpleName
72     private var onLoadCanceller: Runnable? = null
73 
74     companion object {
75         private const val BIND_RETRY_DELAY = 1000L // ms
76         private const val LOAD_TIMEOUT_SECONDS = 20L // seconds
77         private const val MAX_BIND_RETRIES = 5
78         private const val DEBUG = true
79         private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
80             Context.BIND_NOT_PERCEPTIBLE
81         // Use BIND_NOT_PERCEPTIBLE so it will be at lower priority from SystemUI.
82         // However, don't use WAIVE_PRIORITY, as by itself, it will kill the app
83         // once the Task is finished in the device controls panel.
84         private val BIND_FLAGS_PANEL = Context.BIND_AUTO_CREATE or Context.BIND_NOT_PERCEPTIBLE
85     }
86 
<lambda>null87     private val intent = Intent(ControlsProviderService.SERVICE_CONTROLS).apply {
88         component = componentName
89         putExtra(CALLBACK_BUNDLE, Bundle().apply {
90             putBinder(CALLBACK_TOKEN, token)
91         })
92     }
93 
bindServicenull94     private fun bindService(bind: Boolean, forPanel: Boolean = false) {
95         executor.execute {
96             requiresBound = bind
97             if (bind) {
98                 if (bindTryCount != MAX_BIND_RETRIES && wrapper == null) {
99                     if (DEBUG) {
100                         Log.d(TAG, "Binding service $intent")
101                     }
102                     bindTryCount++
103                     try {
104                         val flags = if (forPanel) BIND_FLAGS_PANEL else BIND_FLAGS
105                         val bound = context
106                                 .bindServiceAsUser(intent, serviceConnection, flags, user)
107                         if (!bound) {
108                             context.unbindService(serviceConnection)
109                         }
110                     } catch (e: SecurityException) {
111                         Log.e(TAG, "Failed to bind to service", e)
112                     }
113                 }
114             } else {
115                 if (DEBUG) {
116                     Log.d(TAG, "Unbinding service $intent")
117                 }
118                 bindTryCount = 0
119                 wrapper?.run {
120                     context.unbindService(serviceConnection)
121                 }
122                 wrapper = null
123             }
124         }
125     }
126 
127     private val serviceConnection = object : ServiceConnection {
onServiceConnectednull128         override fun onServiceConnected(name: ComponentName, service: IBinder) {
129             if (DEBUG) Log.d(TAG, "onServiceConnected $name")
130             bindTryCount = 0
131             wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service))
132             try {
133                 service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
134             } catch (_: RemoteException) {}
135             handlePendingServiceMethods()
136         }
137 
onServiceDisconnectednull138         override fun onServiceDisconnected(name: ComponentName?) {
139             if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
140             wrapper = null
141             bindService(false)
142         }
143 
onNullBindingnull144         override fun onNullBinding(name: ComponentName?) {
145             if (DEBUG) Log.d(TAG, "onNullBinding $name")
146             wrapper = null
147             context.unbindService(this)
148         }
149     }
150 
handlePendingServiceMethodsnull151     private fun handlePendingServiceMethods() {
152         val queue = synchronized(queuedServiceMethods) {
153             ArraySet(queuedServiceMethods).also {
154                 queuedServiceMethods.clear()
155             }
156         }
157         queue.forEach {
158             it.run()
159         }
160     }
161 
binderDiednull162     override fun binderDied() {
163         if (wrapper == null) return
164         wrapper = null
165         if (requiresBound) {
166             if (DEBUG) {
167                 Log.d(TAG, "binderDied")
168             }
169             // Try rebinding some time later
170         }
171     }
172 
queueServiceMethodnull173     private fun queueServiceMethod(sm: ServiceMethod) {
174         synchronized(queuedServiceMethods) {
175             queuedServiceMethods.add(sm)
176         }
177     }
178 
invokeOrQueuenull179     private fun invokeOrQueue(sm: ServiceMethod) {
180         wrapper?.run {
181             sm.run()
182         } ?: run {
183             queueServiceMethod(sm)
184             bindService(true)
185         }
186     }
187 
188     /**
189      * Request a call to [IControlsProvider.load].
190      *
191      * If the service is not bound, the call will be queued and the service will be bound first.
192      * The service will be unbound after the controls are returned or the call times out.
193      *
194      * @param subscriber the subscriber that manages coordination for loading controls
195      */
maybeBindAndLoadnull196     fun maybeBindAndLoad(subscriber: IControlsSubscriber.Stub) {
197         onLoadCanceller = executor.executeDelayed({
198             // Didn't receive a response in time, log and send back error
199             Log.d(TAG, "Timeout waiting onLoad for $componentName")
200             subscriber.onError(token, "Timeout waiting onLoad")
201             unbindService()
202         }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
203 
204         invokeOrQueue(Load(subscriber))
205     }
206 
207     /**
208      * Request a call to [IControlsProvider.loadSuggested].
209      *
210      * If the service is not bound, the call will be queued and the service will be bound first.
211      * The service will be unbound if the call times out.
212      *
213      * @param subscriber the subscriber that manages coordination for loading controls
214      */
maybeBindAndLoadSuggestednull215     fun maybeBindAndLoadSuggested(subscriber: IControlsSubscriber.Stub) {
216         onLoadCanceller = executor.executeDelayed({
217             // Didn't receive a response in time, log and send back error
218             Log.d(TAG, "Timeout waiting onLoadSuggested for $componentName")
219             subscriber.onError(token, "Timeout waiting onLoadSuggested")
220             unbindService()
221         }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
222 
223         invokeOrQueue(Suggest(subscriber))
224     }
225 
cancelLoadTimeoutnull226     fun cancelLoadTimeout() {
227         onLoadCanceller?.run()
228         onLoadCanceller = null
229     }
230 
231     /**
232      * Request a subscription to the [Publisher] returned by [ControlsProviderService.publisherFor]
233      *
234      * If the service is not bound, the call will be queued and the service will be bound first.
235      *
236      * @param controlIds a list of the ids of controls to send status back.
237      */
maybeBindAndSubscribenull238     fun maybeBindAndSubscribe(controlIds: List<String>, subscriber: IControlsSubscriber) =
239         invokeOrQueue(Subscribe(controlIds, subscriber))
240 
241     /**
242      * Request a call to [ControlsProviderService.performControlAction].
243      *
244      * If the service is not bound, the call will be queued and the service will be bound first.
245      *
246      * @param controlId the id of the [Control] the action is performed on
247      * @param action the action performed
248      */
249     fun maybeBindAndSendAction(controlId: String, action: ControlAction) =
250         invokeOrQueue(Action(controlId, action))
251 
252     /**
253      * Starts the subscription to the [ControlsProviderService] and requests status of controls.
254      *
255      * @param subscription the subscription to use to request controls
256      * @see maybeBindAndLoad
257      */
258     fun startSubscription(subscription: IControlsSubscription, requestLimit: Long) {
259         if (DEBUG) {
260             Log.d(TAG, "startSubscription: $subscription")
261         }
262 
263         wrapper?.request(subscription, requestLimit)
264     }
265 
266     /**
267      * Cancels the subscription to the [ControlsProviderService].
268      *
269      * @param subscription the subscription to cancel
270      * @see maybeBindAndLoad
271      */
cancelSubscriptionnull272     fun cancelSubscription(subscription: IControlsSubscription) {
273         if (DEBUG) {
274             Log.d(TAG, "cancelSubscription: $subscription")
275         }
276 
277         wrapper?.cancel(subscription)
278     }
279 
280     /**
281      * Request bind to the service.
282      */
bindServicenull283     fun bindService() {
284         bindService(true)
285     }
286 
bindServiceForPanelnull287     fun bindServiceForPanel() {
288         bindService(bind = true, forPanel = true)
289     }
290 
291     /**
292      * Request unbind from the service.
293      */
unbindServicenull294     fun unbindService() {
295         onLoadCanceller?.run()
296         onLoadCanceller = null
297 
298         bindService(false)
299     }
300 
toStringnull301     override fun toString(): String {
302         return StringBuilder("ControlsProviderLifecycleManager(").apply {
303             append("component=$componentName")
304             append(", user=$user")
305             append(")")
306         }.toString()
307     }
308 
309     /**
310      * Service methods that can be queued or invoked, and are retryable for failure scenarios
311      */
312     abstract inner class ServiceMethod {
runnull313         fun run() {
314             if (!callWrapper()) {
315                 queueServiceMethod(this)
316                 binderDied()
317             }
318         }
319 
callWrappernull320         internal abstract fun callWrapper(): Boolean
321     }
322 
323     inner class Load(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
324         override fun callWrapper(): Boolean {
325             if (DEBUG) {
326                 Log.d(TAG, "load $componentName")
327             }
328             return wrapper?.load(subscriber) ?: false
329         }
330     }
331 
332     inner class Suggest(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
callWrappernull333         override fun callWrapper(): Boolean {
334             if (DEBUG) {
335                 Log.d(TAG, "suggest $componentName")
336             }
337             return wrapper?.loadSuggested(subscriber) ?: false
338         }
339     }
340     inner class Subscribe(
341         val list: List<String>,
342         val subscriber: IControlsSubscriber
343     ) : ServiceMethod() {
callWrappernull344         override fun callWrapper(): Boolean {
345             if (DEBUG) {
346                 Log.d(TAG, "subscribe $componentName - $list")
347             }
348 
349             return wrapper?.subscribe(list, subscriber) ?: false
350         }
351     }
352 
353     inner class Action(val id: String, val action: ControlAction) : ServiceMethod() {
callWrappernull354         override fun callWrapper(): Boolean {
355             if (DEBUG) {
356                 Log.d(TAG, "onAction $componentName - $id")
357             }
358             return wrapper?.action(id, action, actionCallbackService) ?: false
359         }
360     }
361 }
362