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