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