blob: e91859365ab6ea469ddd2c68d1c075b308abdb95 [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 */
Santos Cordon74d420b2014-05-07 14:38:47 -070052 interface Listener<ServiceBinderClass extends ServiceBinder<?>> {
53 void onUnbind(ServiceBinderClass serviceBinder);
Santos Cordon5c12c6e2014-02-13 14:35:31 -080054 }
55
Ben Gilad61925612014-03-11 19:06:36 -070056 /**
57 * Helper class to perform on-demand binding.
58 */
59 final class Binder {
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 */
66 void bind(BindCallback callback) {
67 ThreadUtil.checkOnMainThread();
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();
83
Sailesh Nepalc92c4362014-07-04 18:33:21 -070084 Log.d(ServiceBinder.this, "Binding to service with intent: %s", serviceIntent);
Ben Gilad61925612014-03-11 19:06:36 -070085 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
86 handleFailedConnection();
87 return;
88 }
89 } else {
90 Log.d(ServiceBinder.this, "Service is already bound.");
91 Preconditions.checkNotNull(mBinder);
92 handleSuccessfulConnection();
93 }
94 }
95 }
96
Santos Cordon63aeb162014-02-10 09:20:40 -080097 private final class ServiceBinderConnection implements ServiceConnection {
98 @Override
99 public void onServiceConnected(ComponentName componentName, IBinder binder) {
100 ThreadUtil.checkOnMainThread();
Santos Cordonc499c1c2014-04-14 17:13:14 -0700101 Log.i(this, "Service bound %s", componentName);
Santos Cordon63aeb162014-02-10 09:20:40 -0800102
103 // Unbind request was queued so unbind immediately.
104 if (mIsBindingAborted) {
105 clearAbort();
Santos Cordonc499c1c2014-04-14 17:13:14 -0700106 logServiceDisconnected("onServiceConnected");
Santos Cordon63aeb162014-02-10 09:20:40 -0800107 mContext.unbindService(this);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800108 handleFailedConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800109 return;
110 }
111
112 mServiceConnection = this;
Ben Gilad61925612014-03-11 19:06:36 -0700113 setBinder(binder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800114 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800115 }
116
117 @Override
118 public void onServiceDisconnected(ComponentName componentName) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700119 logServiceDisconnected("onServiceDisconnected");
120
Santos Cordon63aeb162014-02-10 09:20:40 -0800121 mServiceConnection = null;
122 clearAbort();
123
124 handleServiceDisconnected();
125 }
126 }
127
128 /** The application context. */
129 private final Context mContext;
130
131 /** The intent action to use when binding through {@link Context#bindService}. */
132 private final String mServiceAction;
133
134 /** The component name of the service to bind to. */
135 private final ComponentName mComponentName;
136
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800137 /** The set of callbacks waiting for notification of the binding's success or failure. */
138 private final Set<BindCallback> mCallbacks = Sets.newHashSet();
139
Santos Cordon63aeb162014-02-10 09:20:40 -0800140 /** Used to bind and unbind from the service. */
141 private ServiceConnection mServiceConnection;
142
143 /** The binder provided by {@link ServiceConnection#onServiceConnected} */
144 private IBinder mBinder;
145
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800146 private int mAssociatedCallCount = 0;
147
Santos Cordon63aeb162014-02-10 09:20:40 -0800148 /**
149 * Indicates that an unbind request was made when the service was not yet bound. If the service
150 * successfully connects when this is true, it should be unbound immediately.
151 */
152 private boolean mIsBindingAborted;
153
154 /**
Santos Cordonc499c1c2014-04-14 17:13:14 -0700155 * Set of currently registered listeners.
156 */
157 private Set<Listener> mListeners = Sets.newHashSet();
158
159 /**
Santos Cordon63aeb162014-02-10 09:20:40 -0800160 * Persists the specified parameters and initializes the new instance.
161 *
162 * @param serviceAction The intent-action used with {@link Context#bindService}.
163 * @param componentName The component name of the service with which to bind.
164 */
165 protected ServiceBinder(String serviceAction, ComponentName componentName) {
166 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
167 Preconditions.checkNotNull(componentName);
168
169 mContext = TelecommApp.getInstance();
170 mServiceAction = serviceAction;
171 mComponentName = componentName;
172 }
173
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800174 final void incrementAssociatedCallCount() {
175 mAssociatedCallCount++;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700176 Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
177 mComponentName.flattenToShortString());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800178 }
179
180 final void decrementAssociatedCallCount() {
181 if (mAssociatedCallCount > 0) {
182 mAssociatedCallCount--;
Santos Cordonc499c1c2014-04-14 17:13:14 -0700183 Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
184 mComponentName.flattenToShortString());
185
186 if (mAssociatedCallCount == 0) {
187 unbind();
188 }
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800189 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800190 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
191 mComponentName.getClassName());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800192 }
193 }
194
195 final int getAssociatedCallCount() {
196 return mAssociatedCallCount;
197 }
198
Santos Cordon63aeb162014-02-10 09:20:40 -0800199 /**
200 * Unbinds from the service if already bound, no-op otherwise.
201 */
202 final void unbind() {
203 ThreadUtil.checkOnMainThread();
204
205 if (mServiceConnection == null) {
206 // We're not yet bound, so queue up an abort request.
207 mIsBindingAborted = true;
208 } else {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700209 logServiceDisconnected("unbind");
Santos Cordon63aeb162014-02-10 09:20:40 -0800210 mContext.unbindService(mServiceConnection);
211 mServiceConnection = null;
Ben Gilad61925612014-03-11 19:06:36 -0700212 setBinder(null);
Santos Cordon63aeb162014-02-10 09:20:40 -0800213 }
214 }
215
Ben Giladbb167cd2014-02-25 16:24:05 -0800216 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800217 return mComponentName;
218 }
219
Ben Gilad61925612014-03-11 19:06:36 -0700220 final boolean isServiceValid(String actionName) {
221 if (mBinder == null) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700222 Log.w(this, "%s invoked while service is unbound", actionName);
Ben Gilad61925612014-03-11 19:06:36 -0700223 return false;
224 }
225
226 return true;
227 }
228
Santos Cordonc499c1c2014-04-14 17:13:14 -0700229 final void addListener(Listener listener) {
230 mListeners.add(listener);
231 }
232
233 final void removeListener(Listener listener) {
234 mListeners.remove(listener);
235 }
236
237 /**
238 * Logs a standard message upon service disconnection. This method exists because there is no
239 * single method called whenever the service unbinds and we want to log the same string in all
240 * instances where that occurs. (Context.unbindService() does not cause onServiceDisconnected
241 * to execute).
242 *
243 * @param sourceTag Tag to disambiguate
244 */
245 private void logServiceDisconnected(String sourceTag) {
246 Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
247 }
248
Santos Cordon63aeb162014-02-10 09:20:40 -0800249 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800250 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
251 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800252 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800253 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800254 for (BindCallback callback : mCallbacks) {
255 callback.onSuccess();
256 }
257 mCallbacks.clear();
258 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800259
260 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800261 * Notifies all the outstanding callbacks that the service failed to bind. The list of
262 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800263 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800264 private void handleFailedConnection() {
265 for (BindCallback callback : mCallbacks) {
266 callback.onFailure();
267 }
268 mCallbacks.clear();
269 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800270
271 /**
272 * Handles a service disconnection.
273 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800274 private void handleServiceDisconnected() {
Ben Gilad61925612014-03-11 19:06:36 -0700275 setBinder(null);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800276 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800277
278 private void clearAbort() {
279 mIsBindingAborted = false;
280 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800281
282 /**
Ben Gilad61925612014-03-11 19:06:36 -0700283 * Sets the (private) binder and updates the child class.
284 *
285 * @param binder The new binder value.
286 */
287 private void setBinder(IBinder binder) {
Santos Cordonc499c1c2014-04-14 17:13:14 -0700288 if (mBinder != binder) {
289 mBinder = binder;
290
291 setServiceInterface(binder);
292
293 if (binder == null) {
294 // Use a copy of the listener list to allow the listeners to unregister themselves
295 // as part of the unbind without causing issues.
296 for (Listener l : ImmutableSet.copyOf(mListeners)) {
297 l.onUnbind(this);
298 }
299 }
300 }
Ben Gilad61925612014-03-11 19:06:36 -0700301 }
302
303 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800304 * Sets the service interface after the service is bound or unbound.
305 *
306 * @param binder The actual bound service implementation.
307 */
308 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800309}