1 /* 2 * Copyright (C) 2014 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.server.telecom; 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.IBinder; 24 import android.os.RemoteException; 25 import android.os.UserHandle; 26 import android.telecom.Log; 27 import android.text.TextUtils; 28 import android.util.ArraySet; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.Preconditions; 32 33 import java.util.Collections; 34 import java.util.Set; 35 import java.util.concurrent.ConcurrentHashMap; 36 37 /** 38 * Abstract class to perform the work of binding and unbinding to the specified service interface. 39 * Subclasses supply the service intent and component name and this class will invoke protected 40 * methods when the class is bound, unbound, or upon failure. 41 */ 42 public abstract class ServiceBinder { 43 44 /** 45 * Callback to notify after a binding succeeds or fails. 46 */ 47 interface BindCallback { onSuccess()48 void onSuccess(); onFailure()49 void onFailure(); 50 } 51 52 /** 53 * Listener for bind events on ServiceBinder. 54 */ 55 interface Listener<ServiceBinderClass extends ServiceBinder> { onUnbind(ServiceBinderClass serviceBinder)56 void onUnbind(ServiceBinderClass serviceBinder); 57 } 58 59 /** 60 * Helper class to perform on-demand binding. 61 */ 62 final class Binder2 { 63 /** 64 * Performs an asynchronous bind to the service (only if not already bound) and executes the 65 * specified callback. 66 * 67 * @param callback The callback to notify of the binding's success or failure. 68 * @param call The call for which we are being bound. 69 */ bind(BindCallback callback, Call call)70 void bind(BindCallback callback, Call call) { 71 Log.d(ServiceBinder.this, "bind()"); 72 73 // Reset any abort request if we're asked to bind again. 74 clearAbort(); 75 76 synchronized (mCallbacks) { 77 if (!mCallbacks.isEmpty()) { 78 // Binding already in progress, append to the list of callbacks and bail out. 79 mCallbacks.add(callback); 80 return; 81 } 82 mCallbacks.add(callback); 83 } 84 85 if (mServiceConnection == null) { 86 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName); 87 ServiceConnection connection = new ServiceBinderConnection(call); 88 89 Log.addEvent(call, LogUtils.Events.BIND_CS, mComponentName); 90 final int bindingFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE 91 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS; 92 final boolean isBound; 93 if (mUserHandle != null) { 94 isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags, 95 mUserHandle); 96 } else { 97 isBound = mContext.bindService(serviceIntent, connection, bindingFlags); 98 } 99 if (!isBound) { 100 handleFailedConnection(); 101 return; 102 } 103 } else { 104 Log.d(ServiceBinder.this, "Service is already bound."); 105 Preconditions.checkNotNull(mBinder); 106 handleSuccessfulConnection(); 107 } 108 } 109 } 110 111 private class ServiceDeathRecipient implements IBinder.DeathRecipient { 112 113 private ComponentName mComponentName; 114 ServiceDeathRecipient(ComponentName name)115 ServiceDeathRecipient(ComponentName name) { 116 mComponentName = name; 117 } 118 119 @Override binderDied()120 public void binderDied() { 121 try { 122 synchronized (mLock) { 123 Log.startSession("SDR.bD", 124 Log.getPackageAbbreviation(mComponentName)); 125 Log.i(this, "binderDied: ConnectionService %s died.", mComponentName); 126 logServiceDisconnected("binderDied"); 127 handleDisconnect(); 128 } 129 } finally { 130 Log.endSession(); 131 } 132 } 133 } 134 135 private final class ServiceBinderConnection implements ServiceConnection { 136 /** 137 * The initial call for which the service was bound. 138 */ 139 private Call mCall; 140 ServiceBinderConnection(Call call)141 ServiceBinderConnection(Call call) { 142 mCall = call; 143 } 144 145 @Override onServiceConnected(ComponentName componentName, IBinder binder)146 public void onServiceConnected(ComponentName componentName, IBinder binder) { 147 try { 148 Log.startSession("SBC.oSC", Log.getPackageAbbreviation(componentName)); 149 synchronized (mLock) { 150 Log.i(this, "Service bound %s", componentName); 151 152 Log.addEvent(mCall, LogUtils.Events.CS_BOUND, componentName); 153 mCall = null; 154 155 // Unbind request was queued so unbind immediately. 156 if (mIsBindingAborted) { 157 clearAbort(); 158 logServiceDisconnected("onServiceConnected"); 159 mContext.unbindService(this); 160 handleFailedConnection(); 161 return; 162 } 163 if (binder != null) { 164 mServiceDeathRecipient = new ServiceDeathRecipient(componentName); 165 try { 166 binder.linkToDeath(mServiceDeathRecipient, 0); 167 mServiceConnection = this; 168 setBinder(binder); 169 handleSuccessfulConnection(); 170 } catch (RemoteException e) { 171 Log.w(this, "onServiceConnected: %s died."); 172 if (mServiceDeathRecipient != null) { 173 mServiceDeathRecipient.binderDied(); 174 } 175 } 176 } 177 } 178 } finally { 179 Log.endSession(); 180 } 181 } 182 183 @Override onServiceDisconnected(ComponentName componentName)184 public void onServiceDisconnected(ComponentName componentName) { 185 try { 186 Log.startSession("SBC.oSD", Log.getPackageAbbreviation(componentName)); 187 synchronized (mLock) { 188 logServiceDisconnected("onServiceDisconnected"); 189 handleDisconnect(); 190 } 191 } finally { 192 Log.endSession(); 193 } 194 } 195 } 196 handleDisconnect()197 private void handleDisconnect() { 198 mServiceConnection = null; 199 clearAbort(); 200 201 handleServiceDisconnected(); 202 } 203 204 /** The application context. */ 205 private final Context mContext; 206 207 /** The Telecom lock object. */ 208 protected final TelecomSystem.SyncRoot mLock; 209 210 /** The intent action to use when binding through {@link Context#bindService}. */ 211 private final String mServiceAction; 212 213 /** The component name of the service to bind to. */ 214 protected final ComponentName mComponentName; 215 216 /** 217 * Abbreviated form of the package name from {@link #mComponentName}; used for session logging. 218 */ 219 protected final String mPackageAbbreviation; 220 221 /** The set of callbacks waiting for notification of the binding's success or failure. */ 222 private final Set<BindCallback> mCallbacks = new ArraySet<>(); 223 224 /** Used to bind and unbind from the service. */ 225 private ServiceConnection mServiceConnection; 226 227 /** Used to handle death of the service. */ 228 private ServiceDeathRecipient mServiceDeathRecipient; 229 230 /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */ 231 private UserHandle mUserHandle; 232 233 /** The binder provided by {@link ServiceConnection#onServiceConnected} */ 234 private IBinder mBinder; 235 236 private int mAssociatedCallCount = 0; 237 238 /** 239 * Indicates that an unbind request was made when the service was not yet bound. If the service 240 * successfully connects when this is true, it should be unbound immediately. 241 */ 242 private boolean mIsBindingAborted; 243 244 /** 245 * Set of currently registered listeners. 246 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 247 * load factor before resizing, 1 means we only expect a single thread to 248 * access the map so make only a single shard 249 */ 250 private final Set<Listener> mListeners = Collections.newSetFromMap( 251 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1)); 252 253 /** 254 * Persists the specified parameters and initializes the new instance. 255 * 256 * @param serviceAction The intent-action used with {@link Context#bindService}. 257 * @param componentName The component name of the service with which to bind. 258 * @param context The context. 259 * @param userHandle The {@link UserHandle} to use for binding. 260 */ ServiceBinder(String serviceAction, ComponentName componentName, Context context, TelecomSystem.SyncRoot lock, UserHandle userHandle)261 protected ServiceBinder(String serviceAction, ComponentName componentName, Context context, 262 TelecomSystem.SyncRoot lock, UserHandle userHandle) { 263 Preconditions.checkState(!TextUtils.isEmpty(serviceAction)); 264 Preconditions.checkNotNull(componentName); 265 266 mContext = context; 267 mLock = lock; 268 mServiceAction = serviceAction; 269 mComponentName = componentName; 270 mPackageAbbreviation = Log.getPackageAbbreviation(componentName); 271 mUserHandle = userHandle; 272 } 273 getUserHandle()274 final UserHandle getUserHandle() { 275 return mUserHandle; 276 } 277 incrementAssociatedCallCount()278 final void incrementAssociatedCallCount() { 279 mAssociatedCallCount++; 280 Log.v(this, "Call count increment %d, %s", mAssociatedCallCount, 281 mComponentName.flattenToShortString()); 282 } 283 decrementAssociatedCallCount()284 final void decrementAssociatedCallCount() { 285 decrementAssociatedCallCount(false /*isSuppressingUnbind*/); 286 } 287 decrementAssociatedCallCount(boolean isSuppressingUnbind)288 final void decrementAssociatedCallCount(boolean isSuppressingUnbind) { 289 if (mAssociatedCallCount > 0) { 290 mAssociatedCallCount--; 291 Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount, 292 mComponentName.flattenToShortString()); 293 294 if (!isSuppressingUnbind && mAssociatedCallCount == 0) { 295 unbind(); 296 } 297 } else { 298 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero", 299 mComponentName.getClassName()); 300 } 301 } 302 getAssociatedCallCount()303 final int getAssociatedCallCount() { 304 return mAssociatedCallCount; 305 } 306 307 /** 308 * Unbinds from the service if already bound, no-op otherwise. 309 */ unbind()310 final void unbind() { 311 if (mServiceConnection == null) { 312 // We're not yet bound, so queue up an abort request. 313 mIsBindingAborted = true; 314 } else { 315 logServiceDisconnected("unbind"); 316 unlinkDeathRecipient(); 317 mContext.unbindService(mServiceConnection); 318 mServiceConnection = null; 319 setBinder(null); 320 } 321 } 322 getComponentName()323 public final ComponentName getComponentName() { 324 return mComponentName; 325 } 326 327 @VisibleForTesting isServiceValid(String actionName)328 public boolean isServiceValid(String actionName) { 329 if (mBinder == null) { 330 Log.w(this, "%s invoked while service is unbound", actionName); 331 return false; 332 } 333 334 return true; 335 } 336 addListener(Listener listener)337 final void addListener(Listener listener) { 338 mListeners.add(listener); 339 } 340 removeListener(Listener listener)341 final void removeListener(Listener listener) { 342 if (listener != null) { 343 mListeners.remove(listener); 344 } 345 } 346 347 /** 348 * Logs a standard message upon service disconnection. This method exists because there is no 349 * single method called whenever the service unbinds and we want to log the same string in all 350 * instances where that occurs. (Context.unbindService() does not cause onServiceDisconnected 351 * to execute). 352 * 353 * @param sourceTag Tag to disambiguate 354 */ logServiceDisconnected(String sourceTag)355 private void logServiceDisconnected(String sourceTag) { 356 Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag); 357 } 358 359 /** 360 * Notifies all the outstanding callbacks that the service is successfully bound. The list of 361 * outstanding callbacks is cleared afterwards. 362 */ handleSuccessfulConnection()363 private void handleSuccessfulConnection() { 364 // Make a copy so that we don't have a deadlock inside one of the callbacks. 365 Set<BindCallback> callbacksCopy = new ArraySet<>(); 366 synchronized (mCallbacks) { 367 callbacksCopy.addAll(mCallbacks); 368 mCallbacks.clear(); 369 } 370 371 for (BindCallback callback : callbacksCopy) { 372 callback.onSuccess(); 373 } 374 } 375 376 /** 377 * Notifies all the outstanding callbacks that the service failed to bind. The list of 378 * outstanding callbacks is cleared afterwards. 379 */ handleFailedConnection()380 private void handleFailedConnection() { 381 // Make a copy so that we don't have a deadlock inside one of the callbacks. 382 Set<BindCallback> callbacksCopy = new ArraySet<>(); 383 synchronized (mCallbacks) { 384 callbacksCopy.addAll(mCallbacks); 385 mCallbacks.clear(); 386 } 387 388 for (BindCallback callback : callbacksCopy) { 389 callback.onFailure(); 390 } 391 } 392 393 /** 394 * Handles a service disconnection. 395 */ handleServiceDisconnected()396 private void handleServiceDisconnected() { 397 unlinkDeathRecipient(); 398 setBinder(null); 399 } 400 401 /** 402 * Handles un-linking the death recipient from the service's binder. 403 */ unlinkDeathRecipient()404 private void unlinkDeathRecipient() { 405 if (mServiceDeathRecipient != null && mBinder != null) { 406 boolean unlinked = mBinder.unlinkToDeath(mServiceDeathRecipient, 0); 407 if (!unlinked) { 408 Log.i(this, "unlinkDeathRecipient: failed to unlink %s", mComponentName); 409 } 410 mServiceDeathRecipient = null; 411 } else { 412 Log.w(this, "unlinkDeathRecipient: death recipient is null."); 413 } 414 } 415 clearAbort()416 private void clearAbort() { 417 mIsBindingAborted = false; 418 } 419 420 /** 421 * Sets the (private) binder and updates the child class. 422 * 423 * @param binder The new binder value. 424 */ setBinder(IBinder binder)425 private void setBinder(IBinder binder) { 426 if (mBinder != binder) { 427 if (binder == null) { 428 removeServiceInterface(); 429 mBinder = null; 430 for (Listener l : mListeners) { 431 l.onUnbind(this); 432 } 433 } else { 434 mBinder = binder; 435 setServiceInterface(binder); 436 } 437 } 438 } 439 440 /** 441 * Sets the service interface after the service is bound. 442 * 443 * @param binder The new binder interface that is being set. 444 */ setServiceInterface(IBinder binder)445 protected abstract void setServiceInterface(IBinder binder); 446 447 /** 448 * Removes the service interface before the service is unbound. 449 */ removeServiceInterface()450 protected abstract void removeServiceInterface(); 451 } 452