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