blob: b5c00507be4022770701ad70e9a418823c8a8818 [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 Cordon5c12c6e2014-02-13 14:35:31 -080028import com.google.common.collect.Sets;
29
30import java.util.Set;
Santos Cordon63aeb162014-02-10 09:20:40 -080031
32/**
33 * Abstract class to perform the work of binding and unbinding to the specified service interface.
34 * Subclasses supply the service intent and component name and this class will invoke protected
35 * methods when the class is bound, unbound, or upon failure.
36 */
37abstract class ServiceBinder<ServiceInterface extends IInterface> {
38
Santos Cordon5c12c6e2014-02-13 14:35:31 -080039 /**
40 * Callback to notify after a binding succeeds or fails.
41 */
42 interface BindCallback {
43 public void onSuccess();
44 public void onFailure();
45 }
46
Ben Gilad61925612014-03-11 19:06:36 -070047 /**
48 * Helper class to perform on-demand binding.
49 */
50 final class Binder {
51 /**
52 * Performs an asynchronous bind to the service (only if not already bound) and executes the
53 * specified callback.
54 *
55 * @param callback The callback to notify of the binding's success or failure.
56 */
57 void bind(BindCallback callback) {
58 ThreadUtil.checkOnMainThread();
59 Log.d(ServiceBinder.this, "bind()");
60
61 // Reset any abort request if we're asked to bind again.
62 clearAbort();
63
64 if (!mCallbacks.isEmpty()) {
65 // Binding already in progress, append to the list of callbacks and bail out.
66 mCallbacks.add(callback);
67 return;
68 }
69
70 mCallbacks.add(callback);
71 if (mServiceConnection == null) {
72 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
73 ServiceConnection connection = new ServiceBinderConnection();
74
75 Log.d(ServiceBinder.this, "Binding to call service with intent: %s", serviceIntent);
76 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
77 handleFailedConnection();
78 return;
79 }
80 } else {
81 Log.d(ServiceBinder.this, "Service is already bound.");
82 Preconditions.checkNotNull(mBinder);
83 handleSuccessfulConnection();
84 }
85 }
86 }
87
Santos Cordon63aeb162014-02-10 09:20:40 -080088 private final class ServiceBinderConnection implements ServiceConnection {
89 @Override
90 public void onServiceConnected(ComponentName componentName, IBinder binder) {
91 ThreadUtil.checkOnMainThread();
92
93 // Unbind request was queued so unbind immediately.
94 if (mIsBindingAborted) {
95 clearAbort();
96 mContext.unbindService(this);
Santos Cordon5c12c6e2014-02-13 14:35:31 -080097 handleFailedConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -080098 return;
99 }
100
101 mServiceConnection = this;
Ben Gilad61925612014-03-11 19:06:36 -0700102 setBinder(binder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800103 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800104 }
105
106 @Override
107 public void onServiceDisconnected(ComponentName componentName) {
108 mServiceConnection = null;
109 clearAbort();
110
111 handleServiceDisconnected();
112 }
113 }
114
115 /** The application context. */
116 private final Context mContext;
117
118 /** The intent action to use when binding through {@link Context#bindService}. */
119 private final String mServiceAction;
120
121 /** The component name of the service to bind to. */
122 private final ComponentName mComponentName;
123
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800124 /** The set of callbacks waiting for notification of the binding's success or failure. */
125 private final Set<BindCallback> mCallbacks = Sets.newHashSet();
126
Santos Cordon63aeb162014-02-10 09:20:40 -0800127 /** Used to bind and unbind from the service. */
128 private ServiceConnection mServiceConnection;
129
130 /** The binder provided by {@link ServiceConnection#onServiceConnected} */
131 private IBinder mBinder;
132
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800133 /** The number of calls currently associated with this service. */
134 private int mAssociatedCallCount = 0;
135
Santos Cordon63aeb162014-02-10 09:20:40 -0800136 /**
137 * Indicates that an unbind request was made when the service was not yet bound. If the service
138 * successfully connects when this is true, it should be unbound immediately.
139 */
140 private boolean mIsBindingAborted;
141
142 /**
143 * Persists the specified parameters and initializes the new instance.
144 *
145 * @param serviceAction The intent-action used with {@link Context#bindService}.
146 * @param componentName The component name of the service with which to bind.
147 */
148 protected ServiceBinder(String serviceAction, ComponentName componentName) {
149 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
150 Preconditions.checkNotNull(componentName);
151
152 mContext = TelecommApp.getInstance();
153 mServiceAction = serviceAction;
154 mComponentName = componentName;
155 }
156
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800157 final void incrementAssociatedCallCount() {
158 mAssociatedCallCount++;
159 }
160
161 final void decrementAssociatedCallCount() {
162 if (mAssociatedCallCount > 0) {
163 mAssociatedCallCount--;
164 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800165 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
166 mComponentName.getClassName());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800167 }
168 }
169
170 final int getAssociatedCallCount() {
171 return mAssociatedCallCount;
172 }
173
Santos Cordon63aeb162014-02-10 09:20:40 -0800174 /**
175 * Unbinds from the service if already bound, no-op otherwise.
176 */
177 final void unbind() {
178 ThreadUtil.checkOnMainThread();
179
180 if (mServiceConnection == null) {
181 // We're not yet bound, so queue up an abort request.
182 mIsBindingAborted = true;
183 } else {
184 mContext.unbindService(mServiceConnection);
185 mServiceConnection = null;
Ben Gilad61925612014-03-11 19:06:36 -0700186 setBinder(null);
Santos Cordon63aeb162014-02-10 09:20:40 -0800187 }
188 }
189
Ben Giladbb167cd2014-02-25 16:24:05 -0800190 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800191 return mComponentName;
192 }
193
Ben Gilad61925612014-03-11 19:06:36 -0700194 final boolean isServiceValid(String actionName) {
195 if (mBinder == null) {
196 Log.wtf(this, "%s invoked while service is unbound", actionName);
197 return false;
198 }
199
200 return true;
201 }
202
Santos Cordon63aeb162014-02-10 09:20:40 -0800203 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800204 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
205 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800206 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800207 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800208 for (BindCallback callback : mCallbacks) {
209 callback.onSuccess();
210 }
211 mCallbacks.clear();
212 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800213
214 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800215 * Notifies all the outstanding callbacks that the service failed to bind. The list of
216 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800217 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800218 private void handleFailedConnection() {
219 for (BindCallback callback : mCallbacks) {
220 callback.onFailure();
221 }
222 mCallbacks.clear();
223 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800224
225 /**
226 * Handles a service disconnection.
227 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800228 private void handleServiceDisconnected() {
Ben Gilad61925612014-03-11 19:06:36 -0700229 setBinder(null);
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800230 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800231
232 private void clearAbort() {
233 mIsBindingAborted = false;
234 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800235
236 /**
Ben Gilad61925612014-03-11 19:06:36 -0700237 * Sets the (private) binder and updates the child class.
238 *
239 * @param binder The new binder value.
240 */
241 private void setBinder(IBinder binder) {
242 mBinder = binder;
243 setServiceInterface(binder);
244 }
245
246 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800247 * Sets the service interface after the service is bound or unbound.
248 *
249 * @param binder The actual bound service implementation.
250 */
251 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800252}