blob: 98ce4cad7b6c8ead7ad6eaeb913015d21cb145d0 [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
Santos Cordon63aeb162014-02-10 09:20:40 -080047 private final class ServiceBinderConnection implements ServiceConnection {
48 @Override
49 public void onServiceConnected(ComponentName componentName, IBinder binder) {
50 ThreadUtil.checkOnMainThread();
51
52 // Unbind request was queued so unbind immediately.
53 if (mIsBindingAborted) {
54 clearAbort();
55 mContext.unbindService(this);
Santos Cordon5c12c6e2014-02-13 14:35:31 -080056 handleFailedConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -080057 return;
58 }
59
60 mServiceConnection = this;
61 mBinder = binder;
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080062 setServiceInterface(binder);
63 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -080064 }
65
66 @Override
67 public void onServiceDisconnected(ComponentName componentName) {
68 mServiceConnection = null;
69 clearAbort();
70
71 handleServiceDisconnected();
72 }
73 }
74
75 /** The application context. */
76 private final Context mContext;
77
78 /** The intent action to use when binding through {@link Context#bindService}. */
79 private final String mServiceAction;
80
81 /** The component name of the service to bind to. */
82 private final ComponentName mComponentName;
83
Santos Cordon5c12c6e2014-02-13 14:35:31 -080084 /** The set of callbacks waiting for notification of the binding's success or failure. */
85 private final Set<BindCallback> mCallbacks = Sets.newHashSet();
86
Santos Cordon63aeb162014-02-10 09:20:40 -080087 /** Used to bind and unbind from the service. */
88 private ServiceConnection mServiceConnection;
89
90 /** The binder provided by {@link ServiceConnection#onServiceConnected} */
91 private IBinder mBinder;
92
Ben Gilad8e55d1d2014-02-26 16:25:56 -080093 /** The number of calls currently associated with this service. */
94 private int mAssociatedCallCount = 0;
95
Santos Cordon63aeb162014-02-10 09:20:40 -080096 /**
97 * Indicates that an unbind request was made when the service was not yet bound. If the service
98 * successfully connects when this is true, it should be unbound immediately.
99 */
100 private boolean mIsBindingAborted;
101
102 /**
103 * Persists the specified parameters and initializes the new instance.
104 *
105 * @param serviceAction The intent-action used with {@link Context#bindService}.
106 * @param componentName The component name of the service with which to bind.
107 */
108 protected ServiceBinder(String serviceAction, ComponentName componentName) {
109 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
110 Preconditions.checkNotNull(componentName);
111
112 mContext = TelecommApp.getInstance();
113 mServiceAction = serviceAction;
114 mComponentName = componentName;
115 }
116
117 /**
118 * Performs an asynchronous bind to the service if not already bound.
119 *
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800120 * @param callback The callback to notify of the binding's success or failure.
Santos Cordon63aeb162014-02-10 09:20:40 -0800121 * @return The result of {#link Context#bindService} or true if already bound.
122 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800123 final boolean bind(BindCallback callback) {
Santos Cordon63aeb162014-02-10 09:20:40 -0800124 ThreadUtil.checkOnMainThread();
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800125 Log.d(this, "bind()");
Santos Cordon63aeb162014-02-10 09:20:40 -0800126
127 // Reset any abort request if we're asked to bind again.
128 clearAbort();
129
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800130 // If we are already waiting on a binding request, simply append to the list of waiting
131 // callbacks.
132 if (!mCallbacks.isEmpty()) {
133 mCallbacks.add(callback);
134 return true;
135 }
136
Sailesh Nepalc3193752014-02-22 14:25:03 -0800137 mCallbacks.add(callback);
Santos Cordon63aeb162014-02-10 09:20:40 -0800138 if (mServiceConnection == null) {
139 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
140 ServiceConnection connection = new ServiceBinderConnection();
141
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800142 Log.d(this, "Binding to call service with intent: %s", serviceIntent);
Santos Cordon63aeb162014-02-10 09:20:40 -0800143 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
144 handleFailedConnection();
145 return false;
146 }
147 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800148 Log.d(this, "Service is already bound.");
Santos Cordon63aeb162014-02-10 09:20:40 -0800149 Preconditions.checkNotNull(mBinder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800150 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800151 }
152
153 return true;
154 }
155
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800156 final void incrementAssociatedCallCount() {
157 mAssociatedCallCount++;
158 }
159
160 final void decrementAssociatedCallCount() {
161 if (mAssociatedCallCount > 0) {
162 mAssociatedCallCount--;
163 } else {
Sailesh Nepalf1c191d2014-03-07 18:17:39 -0800164 Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
165 mComponentName.getClassName());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800166 }
167 }
168
169 final int getAssociatedCallCount() {
170 return mAssociatedCallCount;
171 }
172
Santos Cordon63aeb162014-02-10 09:20:40 -0800173 /**
174 * Unbinds from the service if already bound, no-op otherwise.
175 */
176 final void unbind() {
177 ThreadUtil.checkOnMainThread();
178
179 if (mServiceConnection == null) {
180 // We're not yet bound, so queue up an abort request.
181 mIsBindingAborted = true;
182 } else {
183 mContext.unbindService(mServiceConnection);
184 mServiceConnection = null;
185 mBinder = null;
186 }
187 }
188
Ben Giladbb167cd2014-02-25 16:24:05 -0800189 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800190 return mComponentName;
191 }
192
193 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800194 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
195 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800196 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800197 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800198 for (BindCallback callback : mCallbacks) {
199 callback.onSuccess();
200 }
201 mCallbacks.clear();
202 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800203
204 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800205 * Notifies all the outstanding callbacks that the service failed to bind. The list of
206 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800207 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800208 private void handleFailedConnection() {
209 for (BindCallback callback : mCallbacks) {
210 callback.onFailure();
211 }
212 mCallbacks.clear();
213 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800214
215 /**
216 * Handles a service disconnection.
217 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800218 private void handleServiceDisconnected() {
219 setServiceInterface(null);
220 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800221
222 private void clearAbort() {
223 mIsBindingAborted = false;
224 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800225
226 /**
227 * Sets the service interface after the service is bound or unbound.
228 *
229 * @param binder The actual bound service implementation.
230 */
231 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800232}