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