blob: 3dc86be491c4f56f568ba05d1ff9a33f9c6aaeb1 [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
17package com.android.telecomm;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.IBinder;
24import android.os.IInterface;
25
26import com.google.common.base.Preconditions;
27import com.google.common.base.Strings;
Santos Cordonc499c1c2014-04-14 17:13:14 -070028
29import com.google.common.collect.ImmutableSet;
Santos Cordon5c12c6e2014-02-13 14:35:31 -080030import com.google.common.collect.Sets;
31
32import java.util.Set;
Santos Cordon63aeb162014-02-10 09:20:40 -080033
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<ServiceInterface extends IInterface> {
40
Santos Cordon5c12c6e2014-02-13 14:35:31 -080041 /**
42 * Callback to notify after a binding succeeds or fails.
43 */
44 interface BindCallback {
Santos Cordonc499c1c2014-04-14 17:13:14 -070045 void onSuccess();
46 void onFailure();
47 }
48
49 /**
50 * Listener for bind events on ServiceBinder.
51 */
52 interface Listener {
53 @SuppressWarnings("rawtypes")
54 void onUnbind(ServiceBinder 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 */
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 */
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 call 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
Santos Cordon63aeb162014-02-10 09:20:40 -080098 private final class ServiceBinderConnection implements ServiceConnection {
99 @Override
100 public void onServiceConnected(ComponentName componentName, IBinder binder) {
101 ThreadUtil.checkOnMainThread();
Santos Cordonc499c1c2014-04-14 17:13:14 -0700102 Log.i(this, "Service bound %s", componentName);
Santos Cordon63aeb162014-02-10 09:20:40 -0800103
104 // Unbind request was queued so unbind immediately.
105 if (mIsBindingAborted) {
106 clearAbort();
Santos Cordonc499c1c2014-04-14 17:13:14 -0700107 logServiceDisconnected("onServiceConnected");
Santos Cordon63aeb162014-02-10 09:20:40 -0800108 mContext.unbindService(this);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800109 handleFailedConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800110 return;
111 }
112
113 mServiceConnection = this;
Ben Gilad61925612014-03-11 19:06:36 -0700114 setBinder(binder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800115 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800116 }
117
118 @Override
119 public void onServiceDisconnected(ComponentName componentName) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700120 logServiceDisconnected("onServiceDisconnected");
121
Santos Cordon63aeb162014-02-10 09:20:40 -0800122 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
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800138 /** The set of callbacks waiting for notification of the binding's success or failure. */
139 private final Set<BindCallback> mCallbacks = Sets.newHashSet();
140
Santos Cordon63aeb162014-02-10 09:20:40 -0800141 /** 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
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800147 private int mAssociatedCallCount = 0;
148
Santos Cordon63aeb162014-02-10 09:20:40 -0800149 /**
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 /**
Santos Cordonc499c1c2014-04-14 17:13:14 -0700156 * Set of currently registered listeners.
157 */
158 private Set<Listener> mListeners = Sets.newHashSet();
159
160 /**
Santos Cordon63aeb162014-02-10 09:20:40 -0800161 * Persists the specified parameters and initializes the new instance.
162 *
163 * @param serviceAction The intent-action used with {@link Context#bindService}.
164 * @param componentName The component name of the service with which to bind.
165 */
166 protected ServiceBinder(String serviceAction, ComponentName componentName) {
167 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
168 Preconditions.checkNotNull(componentName);
169
170 mContext = TelecommApp.getInstance();
171 mServiceAction = serviceAction;
172 mComponentName = componentName;
173 }
174
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800175 final void incrementAssociatedCallCount() {
176 mAssociatedCallCount++;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700177 Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
178 mComponentName.flattenToShortString());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800179 }
180
181 final void decrementAssociatedCallCount() {
182 if (mAssociatedCallCount > 0) {
183 mAssociatedCallCount--;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700184 Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
185 mComponentName.flattenToShortString());
186
187 if (mAssociatedCallCount == 0) {
188 unbind();
189 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800190 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800191 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
192 mComponentName.getClassName());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800193 }
194 }
195
196 final int getAssociatedCallCount() {
197 return mAssociatedCallCount;
198 }
199
Santos Cordon63aeb162014-02-10 09:20:40 -0800200 /**
201 * Unbinds from the service if already bound, no-op otherwise.
202 */
203 final void unbind() {
204 ThreadUtil.checkOnMainThread();
205
206 if (mServiceConnection == null) {
207 // We're not yet bound, so queue up an abort request.
208 mIsBindingAborted = true;
209 } else {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700210 logServiceDisconnected("unbind");
Santos Cordon63aeb162014-02-10 09:20:40 -0800211 mContext.unbindService(mServiceConnection);
212 mServiceConnection = null;
Ben Gilad61925612014-03-11 19:06:36 -0700213 setBinder(null);
Santos Cordon63aeb162014-02-10 09:20:40 -0800214 }
215 }
216
Ben Giladbb167cd2014-02-25 16:24:05 -0800217 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800218 return mComponentName;
219 }
220
Ben Gilad61925612014-03-11 19:06:36 -0700221 final boolean isServiceValid(String actionName) {
222 if (mBinder == null) {
223 Log.wtf(this, "%s invoked while service is unbound", actionName);
224 return false;
225 }
226
227 return true;
228 }
229
Santos Cordonc499c1c2014-04-14 17:13:14 -0700230 final void addListener(Listener listener) {
231 mListeners.add(listener);
232 }
233
234 final void removeListener(Listener listener) {
235 mListeners.remove(listener);
236 }
237
238 /**
239 * Logs a standard message upon service disconnection. This method exists because there is no
240 * single method called whenever the service unbinds and we want to log the same string in all
241 * instances where that occurs. (Context.unbindService() does not cause onServiceDisconnected
242 * to execute).
243 *
244 * @param sourceTag Tag to disambiguate
245 */
246 private void logServiceDisconnected(String sourceTag) {
247 Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
248 }
249
Santos Cordon63aeb162014-02-10 09:20:40 -0800250 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800251 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
252 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800253 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800254 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800255 for (BindCallback callback : mCallbacks) {
256 callback.onSuccess();
257 }
258 mCallbacks.clear();
259 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800260
261 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800262 * Notifies all the outstanding callbacks that the service failed to bind. The list of
263 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800264 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800265 private void handleFailedConnection() {
266 for (BindCallback callback : mCallbacks) {
267 callback.onFailure();
268 }
269 mCallbacks.clear();
270 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800271
272 /**
273 * Handles a service disconnection.
274 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800275 private void handleServiceDisconnected() {
Ben Gilad61925612014-03-11 19:06:36 -0700276 setBinder(null);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800277 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800278
279 private void clearAbort() {
280 mIsBindingAborted = false;
281 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800282
283 /**
Ben Gilad61925612014-03-11 19:06:36 -0700284 * Sets the (private) binder and updates the child class.
285 *
286 * @param binder The new binder value.
287 */
288 private void setBinder(IBinder binder) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700289 if (mBinder != binder) {
290 mBinder = binder;
291
292 setServiceInterface(binder);
293
294 if (binder == null) {
295 // Use a copy of the listener list to allow the listeners to unregister themselves
296 // as part of the unbind without causing issues.
297 for (Listener l : ImmutableSet.copyOf(mListeners)) {
298 l.onUnbind(this);
299 }
300 }
301 }
Ben Gilad61925612014-03-11 19:06:36 -0700302 }
303
304 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800305 * Sets the service interface after the service is bound or unbound.
306 *
307 * @param binder The actual bound service implementation.
308 */
309 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800310}