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