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