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