blob: a6af6a11d8b73e5897d544a0994b4c9ff325474a [file] [log] [blame]
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -05001/*
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
17package com.android.systemui.controls.controller
18
19import android.content.ComponentName
20import android.content.Context
21import android.content.Intent
22import android.content.ServiceConnection
23import android.os.Binder
24import android.os.Bundle
25import android.os.IBinder
26import android.os.RemoteException
Fabian Kozynski7988bd42020-01-30 12:21:52 -050027import android.os.UserHandle
Fabian Kozynski9c459e52020-02-12 09:08:15 -050028import android.service.controls.ControlsProviderService
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050029import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
30import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
Fabian Kozynski1bb26b52020-01-08 18:20:36 -050031import android.service.controls.IControlsActionCallback
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050032import android.service.controls.IControlsProvider
Fabian Kozynski1bb26b52020-01-08 18:20:36 -050033import android.service.controls.IControlsSubscriber
34import android.service.controls.IControlsSubscription
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050035import android.service.controls.actions.ControlAction
36import android.util.ArraySet
37import android.util.Log
38import com.android.internal.annotations.GuardedBy
39import com.android.systemui.util.concurrency.DelayableExecutor
40import java.util.concurrent.TimeUnit
41
Fabian Kozynski9c459e52020-02-12 09:08:15 -050042/**
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
Fabian Kozynski9c459e52020-02-12 09:08:15 -050050 * @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 */
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050057class ControlsProviderLifecycleManager(
58 private val context: Context,
59 private val executor: DelayableExecutor,
Fabian Kozynski1bb26b52020-01-08 18:20:36 -050060 private val actionCallbackService: IControlsActionCallback.Stub,
Fabian Kozynski7988bd42020-01-30 12:21:52 -050061 val user: UserHandle,
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050062 val componentName: ComponentName
63) : IBinder.DeathRecipient {
64
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050065 val token: IBinder = Binder()
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050066 private var requiresBound = false
Matt Pietal61266442020-03-17 12:53:44 -040067 @GuardedBy("queuedServiceMethods")
68 private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
Fabian Kozynski1bb26b52020-01-08 18:20:36 -050069 private var wrapper: ServiceWrapper? = null
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050070 private var bindTryCount = 0
71 private val TAG = javaClass.simpleName
72 private var onLoadCanceller: Runnable? = null
73
74 companion object {
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050075 private const val BIND_RETRY_DELAY = 1000L // ms
Fabian Kozynski2833f452020-02-14 16:50:04 -050076 private const val LOAD_TIMEOUT_SECONDS = 30L // seconds
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050077 private const val MAX_BIND_RETRIES = 5
78 private const val DEBUG = true
Matt Pietal94ff20f2020-03-23 15:37:46 -040079 private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050080 }
81
82 private val intent = Intent().apply {
83 component = componentName
84 putExtra(CALLBACK_BUNDLE, Bundle().apply {
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -050085 putBinder(CALLBACK_TOKEN, token)
86 })
87 }
88
89 private fun bindService(bind: Boolean) {
Matt Pietal313f37d2020-02-24 11:27:22 -050090 executor.execute {
91 requiresBound = bind
92 if (bind) {
93 if (bindTryCount != MAX_BIND_RETRIES) {
94 if (DEBUG) {
95 Log.d(TAG, "Binding service $intent")
96 }
97 bindTryCount++
98 try {
99 context.bindServiceAsUser(intent, serviceConnection, BIND_FLAGS, user)
100 } catch (e: SecurityException) {
101 Log.e(TAG, "Failed to bind to service", e)
102 }
103 }
104 } else {
105 if (DEBUG) {
106 Log.d(TAG, "Unbinding service $intent")
107 }
108 bindTryCount = 0
109 wrapper?.run {
110 context.unbindService(serviceConnection)
111 }
112 wrapper = null
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500113 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500114 }
115 }
116
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500117 private val serviceConnection = object : ServiceConnection {
118 override fun onServiceConnected(name: ComponentName, service: IBinder) {
119 if (DEBUG) Log.d(TAG, "onServiceConnected $name")
120 bindTryCount = 0
Fabian Kozynski1bb26b52020-01-08 18:20:36 -0500121 wrapper = ServiceWrapper(IControlsProvider.Stub.asInterface(service))
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500122 try {
123 service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
124 } catch (_: RemoteException) {}
Matt Pietal61266442020-03-17 12:53:44 -0400125 handlePendingServiceMethods()
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500126 }
127
128 override fun onServiceDisconnected(name: ComponentName?) {
129 if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
Matt Pietal019feaa2020-01-31 14:51:37 -0500130 wrapper = null
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500131 bindService(false)
132 }
133 }
134
Matt Pietal61266442020-03-17 12:53:44 -0400135 private fun handlePendingServiceMethods() {
136 val queue = synchronized(queuedServiceMethods) {
137 ArraySet(queuedServiceMethods).also {
138 queuedServiceMethods.clear()
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500139 }
140 }
Matt Pietal61266442020-03-17 12:53:44 -0400141 queue.forEach {
142 it.run()
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500143 }
144 }
145
146 override fun binderDied() {
147 if (wrapper == null) return
148 wrapper = null
149 if (requiresBound) {
150 if (DEBUG) {
151 Log.d(TAG, "binderDied")
152 }
153 // Try rebinding some time later
154 }
155 }
156
Matt Pietal61266442020-03-17 12:53:44 -0400157 private fun queueServiceMethod(sm: ServiceMethod) {
158 synchronized(queuedServiceMethods) {
159 queuedServiceMethods.add(sm)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500160 }
161 }
162
Matt Pietal61266442020-03-17 12:53:44 -0400163 private fun invokeOrQueue(sm: ServiceMethod) {
Matt Pietal019feaa2020-01-31 14:51:37 -0500164 wrapper?.run {
Matt Pietal61266442020-03-17 12:53:44 -0400165 sm.run()
Matt Pietal019feaa2020-01-31 14:51:37 -0500166 } ?: run {
Matt Pietal61266442020-03-17 12:53:44 -0400167 queueServiceMethod(sm)
Matt Pietal019feaa2020-01-31 14:51:37 -0500168 bindService(true)
169 }
170 }
171
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500172 /**
Matt Pietald0553b02020-02-13 07:08:58 -0500173 * Request a call to [IControlsProvider.load].
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500174 *
175 * If the service is not bound, the call will be queued and the service will be bound first.
Matt Pietald0553b02020-02-13 07:08:58 -0500176 * The service will be unbound after the controls are returned or the call times out.
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500177 *
Matt Pietald0553b02020-02-13 07:08:58 -0500178 * @param subscriber the subscriber that manages coordination for loading controls
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500179 */
Matt Pietald0553b02020-02-13 07:08:58 -0500180 fun maybeBindAndLoad(subscriber: IControlsSubscriber.Stub) {
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500181 onLoadCanceller = executor.executeDelayed({
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500182 // Didn't receive a response in time, log and send back error
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500183 Log.d(TAG, "Timeout waiting onLoad for $componentName")
Matt Pietald0553b02020-02-13 07:08:58 -0500184 subscriber.onError(token, "Timeout waiting onLoad")
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500185 unbindService()
Fabian Kozynski2833f452020-02-14 16:50:04 -0500186 }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
Matt Pietal019feaa2020-01-31 14:51:37 -0500187
Matt Pietal61266442020-03-17 12:53:44 -0400188 invokeOrQueue(Load(subscriber))
189 }
190
191 /**
192 * Request a call to [IControlsProvider.loadSuggested].
193 *
194 * If the service is not bound, the call will be queued and the service will be bound first.
Matt Pietalcd757c82020-04-08 10:20:48 -0400195 * The service will be unbound if the call times out.
Matt Pietal61266442020-03-17 12:53:44 -0400196 *
197 * @param subscriber the subscriber that manages coordination for loading controls
198 */
199 fun maybeBindAndLoadSuggested(subscriber: IControlsSubscriber.Stub) {
200 onLoadCanceller = executor.executeDelayed({
201 // Didn't receive a response in time, log and send back error
202 Log.d(TAG, "Timeout waiting onLoadSuggested for $componentName")
203 subscriber.onError(token, "Timeout waiting onLoadSuggested")
204 unbindService()
205 }, LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
206
207 invokeOrQueue(Suggest(subscriber))
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500208 }
209
Fabian Kozynski8b14c302020-03-11 17:20:38 -0400210 fun cancelLoadTimeout() {
211 onLoadCanceller?.run()
212 onLoadCanceller = null
213 }
214
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500215 /**
216 * Request a subscription to the [Publisher] returned by [ControlsProviderService.publisherFor]
217 *
218 * If the service is not bound, the call will be queued and the service will be bound first.
219 *
220 * @param controlIds a list of the ids of controls to send status back.
221 */
Matt Pietal61266442020-03-17 12:53:44 -0400222 fun maybeBindAndSubscribe(controlIds: List<String>, subscriber: IControlsSubscriber) =
223 invokeOrQueue(Subscribe(controlIds, subscriber))
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500224
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500225 /**
226 * Request a call to [ControlsProviderService.performControlAction].
227 *
228 * If the service is not bound, the call will be queued and the service will be bound first.
229 *
230 * @param controlId the id of the [Control] the action is performed on
231 * @param action the action performed
232 */
Matt Pietal61266442020-03-17 12:53:44 -0400233 fun maybeBindAndSendAction(controlId: String, action: ControlAction) =
234 invokeOrQueue(Action(controlId, action))
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500235
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500236 /**
237 * Starts the subscription to the [ControlsProviderService] and requests status of controls.
238 *
Matt Pietal26fd9a02020-03-10 14:59:25 -0400239 * @param subscription the subscription to use to request controls
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500240 * @see maybeBindAndLoad
241 */
Matt Pietal61266442020-03-17 12:53:44 -0400242 fun startSubscription(subscription: IControlsSubscription, requestLimit: Long) {
Matt Pietal26fd9a02020-03-10 14:59:25 -0400243 if (DEBUG) {
244 Log.d(TAG, "startSubscription: $subscription")
245 }
Matt Pietalcd757c82020-04-08 10:20:48 -0400246
Matt Pietal61266442020-03-17 12:53:44 -0400247 wrapper?.request(subscription, requestLimit)
Fabian Kozynski1bb26b52020-01-08 18:20:36 -0500248 }
249
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500250 /**
Matt Pietal26fd9a02020-03-10 14:59:25 -0400251 * Cancels the subscription to the [ControlsProviderService].
252 *
253 * @param subscription the subscription to cancel
254 * @see maybeBindAndLoad
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500255 */
Matt Pietal26fd9a02020-03-10 14:59:25 -0400256 fun cancelSubscription(subscription: IControlsSubscription) {
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500257 if (DEBUG) {
Matt Pietal26fd9a02020-03-10 14:59:25 -0400258 Log.d(TAG, "cancelSubscription: $subscription")
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500259 }
Matt Pietalcd757c82020-04-08 10:20:48 -0400260
Matt Pietal26fd9a02020-03-10 14:59:25 -0400261 wrapper?.cancel(subscription)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500262 }
263
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500264 /**
265 * Request bind to the service.
266 */
Matt Pietal019feaa2020-01-31 14:51:37 -0500267 fun bindService() {
Matt Pietal019feaa2020-01-31 14:51:37 -0500268 bindService(true)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500269 }
270
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500271 /**
272 * Request unbind from the service.
273 */
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500274 fun unbindService() {
Matt Pietal019feaa2020-01-31 14:51:37 -0500275 onLoadCanceller?.run()
276 onLoadCanceller = null
277
278 bindService(false)
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500279 }
280
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500281 override fun toString(): String {
282 return StringBuilder("ControlsProviderLifecycleManager(").apply {
283 append("component=$componentName")
284 append(", user=$user")
Fabian Kozynski7988bd42020-01-30 12:21:52 -0500285 append(")")
286 }.toString()
287 }
288
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500289 /**
Matt Pietal61266442020-03-17 12:53:44 -0400290 * Service methods that can be queued or invoked, and are retryable for failure scenarios
Fabian Kozynski9c459e52020-02-12 09:08:15 -0500291 */
Matt Pietal61266442020-03-17 12:53:44 -0400292 abstract inner class ServiceMethod {
293 fun run() {
294 if (!callWrapper()) {
295 queueServiceMethod(this)
296 binderDied()
297 }
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500298 }
Matt Pietal61266442020-03-17 12:53:44 -0400299
300 internal abstract fun callWrapper(): Boolean
301 }
302
303 inner class Load(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
304 override fun callWrapper(): Boolean {
305 if (DEBUG) {
306 Log.d(TAG, "load $componentName")
307 }
308 return wrapper?.load(subscriber) ?: false
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500309 }
Matt Pietal61266442020-03-17 12:53:44 -0400310 }
311
312 inner class Suggest(val subscriber: IControlsSubscriber.Stub) : ServiceMethod() {
313 override fun callWrapper(): Boolean {
314 if (DEBUG) {
315 Log.d(TAG, "suggest $componentName")
316 }
317 return wrapper?.loadSuggested(subscriber) ?: false
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500318 }
Matt Pietal61266442020-03-17 12:53:44 -0400319 }
320 inner class Subscribe(
321 val list: List<String>,
322 val subscriber: IControlsSubscriber
323 ) : ServiceMethod() {
324 override fun callWrapper(): Boolean {
325 if (DEBUG) {
326 Log.d(TAG, "subscribe $componentName - $list")
327 }
328
329 return wrapper?.subscribe(list, subscriber) ?: false
330 }
331 }
332
333 inner class Action(val id: String, val action: ControlAction) : ServiceMethod() {
334 override fun callWrapper(): Boolean {
335 if (DEBUG) {
336 Log.d(TAG, "onAction $componentName - $id")
337 }
338 return wrapper?.action(id, action, actionCallbackService) ?: false
Fabian Kozynskif10b6ab2019-12-27 09:31:04 -0500339 }
340 }
Matt Pietal019feaa2020-01-31 14:51:37 -0500341}