blob: 18d65819b05bb034c80eeded4c9f0182a2e01366 [file] [log] [blame]
Santos Cordon63aeb162014-02-10 09:20:40 -08001/*
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
Tyler Gunn7cc70b42014-09-12 22:17:27 -070017package com.android.server.telecom;
Santos Cordon63aeb162014-02-10 09:20:40 -080018
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.IBinder;
Evan Charlton105d9772014-11-25 14:08:53 -080024import android.os.UserHandle;
Santos Cordon4bc02452014-11-19 15:51:12 -080025import android.text.TextUtils;
26import android.util.ArraySet;
Santos Cordon63aeb162014-02-10 09:20:40 -080027
Brad Ebingerf1900072015-11-12 17:25:06 -080028import com.android.internal.annotations.VisibleForTesting;
Tyler Gunn91d43cf2014-09-17 12:19:39 -070029import com.android.internal.util.Preconditions;
Santos Cordon5c12c6e2014-02-13 14:35:31 -080030
Jay Shraunera82c8f72014-08-14 15:49:16 -070031import java.util.Collections;
Santos Cordon5c12c6e2014-02-13 14:35:31 -080032import java.util.Set;
Jay Shraunera82c8f72014-08-14 15:49:16 -070033import java.util.concurrent.ConcurrentHashMap;
Santos Cordon63aeb162014-02-10 09:20:40 -080034
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 */
Ihab Awad78a5e6b2015-02-06 10:13:05 -080040abstract class ServiceBinder {
Santos Cordon63aeb162014-02-10 09:20:40 -080041
Santos Cordon5c12c6e2014-02-13 14:35:31 -080042 /**
43 * Callback to notify after a binding succeeds or fails.
44 */
45 interface BindCallback {
Santos Cordonc499c1c2014-04-14 17:13:14 -070046 void onSuccess();
47 void onFailure();
48 }
49
50 /**
51 * Listener for bind events on ServiceBinder.
52 */
Ihab Awad78a5e6b2015-02-06 10:13:05 -080053 interface Listener<ServiceBinderClass extends ServiceBinder> {
Santos Cordon74d420b2014-05-07 14:38:47 -070054 void onUnbind(ServiceBinderClass serviceBinder);
Santos Cordon5c12c6e2014-02-13 14:35:31 -080055 }
56
Ben Gilad61925612014-03-11 19:06:36 -070057 /**
58 * Helper class to perform on-demand binding.
59 */
Ihab Awad8731faf2015-03-26 13:43:22 -070060 final class Binder2 {
Ben Gilad61925612014-03-11 19:06:36 -070061 /**
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.
Santos Cordon165c1ce2015-07-10 16:07:59 -070066 * @param call The call for which we are being bound.
Ben Gilad61925612014-03-11 19:06:36 -070067 */
Santos Cordon165c1ce2015-07-10 16:07:59 -070068 void bind(BindCallback callback, Call call) {
Ben Gilad61925612014-03-11 19:06:36 -070069 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);
Santos Cordon165c1ce2015-07-10 16:07:59 -070083 ServiceConnection connection = new ServiceBinderConnection(call);
Ben Gilad61925612014-03-11 19:06:36 -070084
Santos Cordon165c1ce2015-07-10 16:07:59 -070085 Log.event(call, Log.Events.BIND_CS, mComponentName);
Santos Cordon8e6edbf2015-05-29 12:25:37 -070086 final int bindingFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
87 final boolean isBound;
Evan Charlton105d9772014-11-25 14:08:53 -080088 if (mUserHandle != null) {
Santos Cordon8e6edbf2015-05-29 12:25:37 -070089 isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags,
90 mUserHandle);
Evan Charlton105d9772014-11-25 14:08:53 -080091 } else {
Santos Cordon8e6edbf2015-05-29 12:25:37 -070092 isBound = mContext.bindService(serviceIntent, connection, bindingFlags);
Evan Charlton105d9772014-11-25 14:08:53 -080093 }
Santos Cordon8e6edbf2015-05-29 12:25:37 -070094 if (!isBound) {
Ben Gilad61925612014-03-11 19:06:36 -070095 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
Santos Cordon63aeb162014-02-10 09:20:40 -0800106 private final class ServiceBinderConnection implements ServiceConnection {
Santos Cordon165c1ce2015-07-10 16:07:59 -0700107 /**
108 * The initial call for which the service was bound.
109 */
110 private Call mCall;
111
112 ServiceBinderConnection(Call call) {
113 mCall = call;
114 }
115
Santos Cordon63aeb162014-02-10 09:20:40 -0800116 @Override
117 public void onServiceConnected(ComponentName componentName, IBinder binder) {
Brad Ebinger11623a32015-11-25 13:52:02 -0800118 try {
119 Log.startSession("SBC.oSC");
120 synchronized (mLock) {
121 Log.i(this, "Service bound %s", componentName);
Santos Cordon63aeb162014-02-10 09:20:40 -0800122
Brad Ebinger11623a32015-11-25 13:52:02 -0800123 Log.event(mCall, Log.Events.CS_BOUND, componentName);
124 mCall = null;
Santos Cordon165c1ce2015-07-10 16:07:59 -0700125
Brad Ebinger11623a32015-11-25 13:52:02 -0800126 // 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();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700138 }
Brad Ebinger11623a32015-11-25 13:52:02 -0800139 } finally {
140 Log.endSession();
Santos Cordon63aeb162014-02-10 09:20:40 -0800141 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800142 }
143
144 @Override
145 public void onServiceDisconnected(ComponentName componentName) {
Brad Ebinger11623a32015-11-25 13:52:02 -0800146 try {
147 Log.startSession("SBC.oSD");
148 synchronized (mLock) {
149 logServiceDisconnected("onServiceDisconnected");
Santos Cordonc499c1c2014-04-14 17:13:14 -0700150
Brad Ebinger11623a32015-11-25 13:52:02 -0800151 mServiceConnection = null;
152 clearAbort();
Santos Cordon63aeb162014-02-10 09:20:40 -0800153
Brad Ebinger11623a32015-11-25 13:52:02 -0800154 handleServiceDisconnected();
155 }
156 } finally {
157 Log.endSession();
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700158 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800159 }
160 }
161
162 /** The application context. */
163 private final Context mContext;
164
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700165 /** The Telecom lock object. */
166 protected final TelecomSystem.SyncRoot mLock;
167
Santos Cordon63aeb162014-02-10 09:20:40 -0800168 /** 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 private final ComponentName mComponentName;
173
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800174 /** The set of callbacks waiting for notification of the binding's success or failure. */
Santos Cordon4bc02452014-11-19 15:51:12 -0800175 private final Set<BindCallback> mCallbacks = new ArraySet<>();
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800176
Santos Cordon63aeb162014-02-10 09:20:40 -0800177 /** Used to bind and unbind from the service. */
178 private ServiceConnection mServiceConnection;
179
Evan Charlton105d9772014-11-25 14:08:53 -0800180 /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */
181 private UserHandle mUserHandle;
182
Santos Cordon63aeb162014-02-10 09:20:40 -0800183 /** The binder provided by {@link ServiceConnection#onServiceConnected} */
184 private IBinder mBinder;
185
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800186 private int mAssociatedCallCount = 0;
187
Santos Cordon63aeb162014-02-10 09:20:40 -0800188 /**
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 /**
Santos Cordonc499c1c2014-04-14 17:13:14 -0700195 * Set of currently registered listeners.
Jay Shraunera82c8f72014-08-14 15:49:16 -0700196 * 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
Santos Cordonc499c1c2014-04-14 17:13:14 -0700199 */
Jay Shraunera82c8f72014-08-14 15:49:16 -0700200 private final Set<Listener> mListeners = Collections.newSetFromMap(
201 new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
Santos Cordonc499c1c2014-04-14 17:13:14 -0700202
203 /**
Santos Cordon63aeb162014-02-10 09:20:40 -0800204 * 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.
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700208 * @param context The context.
Evan Charlton105d9772014-11-25 14:08:53 -0800209 * @param userHandle The {@link UserHandle} to use for binding.
Santos Cordon63aeb162014-02-10 09:20:40 -0800210 */
Evan Charlton105d9772014-11-25 14:08:53 -0800211 protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700212 TelecomSystem.SyncRoot lock, UserHandle userHandle) {
Santos Cordon4bc02452014-11-19 15:51:12 -0800213 Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
Santos Cordon63aeb162014-02-10 09:20:40 -0800214 Preconditions.checkNotNull(componentName);
215
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700216 mContext = context;
Ihab Awad8d5d9dd2015-03-12 11:11:06 -0700217 mLock = lock;
Santos Cordon63aeb162014-02-10 09:20:40 -0800218 mServiceAction = serviceAction;
219 mComponentName = componentName;
Evan Charlton105d9772014-11-25 14:08:53 -0800220 mUserHandle = userHandle;
Santos Cordon63aeb162014-02-10 09:20:40 -0800221 }
222
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800223 final void incrementAssociatedCallCount() {
224 mAssociatedCallCount++;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700225 Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
226 mComponentName.flattenToShortString());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800227 }
228
229 final void decrementAssociatedCallCount() {
230 if (mAssociatedCallCount > 0) {
231 mAssociatedCallCount--;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700232 Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
233 mComponentName.flattenToShortString());
234
235 if (mAssociatedCallCount == 0) {
236 unbind();
237 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800238 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800239 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
240 mComponentName.getClassName());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800241 }
242 }
243
244 final int getAssociatedCallCount() {
245 return mAssociatedCallCount;
246 }
247
Santos Cordon63aeb162014-02-10 09:20:40 -0800248 /**
249 * Unbinds from the service if already bound, no-op otherwise.
250 */
251 final void unbind() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800252 if (mServiceConnection == null) {
253 // We're not yet bound, so queue up an abort request.
254 mIsBindingAborted = true;
255 } else {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700256 logServiceDisconnected("unbind");
Santos Cordon63aeb162014-02-10 09:20:40 -0800257 mContext.unbindService(mServiceConnection);
258 mServiceConnection = null;
Ben Gilad61925612014-03-11 19:06:36 -0700259 setBinder(null);
Santos Cordon63aeb162014-02-10 09:20:40 -0800260 }
261 }
262
Ben Giladbb167cd2014-02-25 16:24:05 -0800263 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800264 return mComponentName;
265 }
266
Brad Ebingerf1900072015-11-12 17:25:06 -0800267 @VisibleForTesting
268 public boolean isServiceValid(String actionName) {
Ben Gilad61925612014-03-11 19:06:36 -0700269 if (mBinder == null) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700270 Log.w(this, "%s invoked while service is unbound", actionName);
Ben Gilad61925612014-03-11 19:06:36 -0700271 return false;
272 }
273
274 return true;
275 }
276
Santos Cordonc499c1c2014-04-14 17:13:14 -0700277 final void addListener(Listener listener) {
278 mListeners.add(listener);
279 }
280
281 final void removeListener(Listener listener) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700282 if (listener != null) {
283 mListeners.remove(listener);
284 }
Santos Cordonc499c1c2014-04-14 17:13:14 -0700285 }
286
287 /**
288 * Logs a standard message upon service disconnection. This method exists because there is no
289 * single method called whenever the service unbinds and we want to log the same string in all
290 * instances where that occurs. (Context.unbindService() does not cause onServiceDisconnected
291 * to execute).
292 *
293 * @param sourceTag Tag to disambiguate
294 */
295 private void logServiceDisconnected(String sourceTag) {
296 Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
297 }
298
Santos Cordon63aeb162014-02-10 09:20:40 -0800299 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800300 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
301 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800302 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800303 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800304 for (BindCallback callback : mCallbacks) {
305 callback.onSuccess();
306 }
307 mCallbacks.clear();
308 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800309
310 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800311 * Notifies all the outstanding callbacks that the service failed to bind. The list of
312 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800313 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800314 private void handleFailedConnection() {
315 for (BindCallback callback : mCallbacks) {
316 callback.onFailure();
317 }
318 mCallbacks.clear();
319 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800320
321 /**
322 * Handles a service disconnection.
323 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800324 private void handleServiceDisconnected() {
Ben Gilad61925612014-03-11 19:06:36 -0700325 setBinder(null);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800326 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800327
328 private void clearAbort() {
329 mIsBindingAborted = false;
330 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800331
332 /**
Ben Gilad61925612014-03-11 19:06:36 -0700333 * Sets the (private) binder and updates the child class.
334 *
335 * @param binder The new binder value.
336 */
337 private void setBinder(IBinder binder) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700338 if (mBinder != binder) {
339 mBinder = binder;
340
341 setServiceInterface(binder);
342
343 if (binder == null) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700344 for (Listener l : mListeners) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700345 l.onUnbind(this);
346 }
347 }
348 }
Ben Gilad61925612014-03-11 19:06:36 -0700349 }
350
351 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800352 * Sets the service interface after the service is bound or unbound.
353 *
354 * @param binder The actual bound service implementation.
355 */
356 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800357}