blob: 2b9cc5cf9195b22cc4370a30e6b3a819c4f57e56 [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;
Santos Cordon493e8f22014-02-19 03:15:12 -080025import android.util.Log;
Santos Cordon63aeb162014-02-10 09:20:40 -080026
27import com.google.common.base.Preconditions;
28import com.google.common.base.Strings;
Santos Cordon5c12c6e2014-02-13 14:35:31 -080029import com.google.common.collect.Sets;
30
31import java.util.Set;
Santos Cordon63aeb162014-02-10 09:20:40 -080032
33/**
34 * Abstract class to perform the work of binding and unbinding to the specified service interface.
35 * Subclasses supply the service intent and component name and this class will invoke protected
36 * methods when the class is bound, unbound, or upon failure.
37 */
38abstract class ServiceBinder<ServiceInterface extends IInterface> {
39
Santos Cordon5c12c6e2014-02-13 14:35:31 -080040 /**
41 * Callback to notify after a binding succeeds or fails.
42 */
43 interface BindCallback {
44 public void onSuccess();
45 public void onFailure();
46 }
47
Santos Cordon63aeb162014-02-10 09:20:40 -080048 private final class ServiceBinderConnection implements ServiceConnection {
49 @Override
50 public void onServiceConnected(ComponentName componentName, IBinder binder) {
51 ThreadUtil.checkOnMainThread();
52
53 // Unbind request was queued so unbind immediately.
54 if (mIsBindingAborted) {
55 clearAbort();
56 mContext.unbindService(this);
Santos Cordon5c12c6e2014-02-13 14:35:31 -080057 handleFailedConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -080058 return;
59 }
60
61 mServiceConnection = this;
62 mBinder = binder;
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080063 setServiceInterface(binder);
64 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -080065 }
66
67 @Override
68 public void onServiceDisconnected(ComponentName componentName) {
69 mServiceConnection = null;
70 clearAbort();
71
72 handleServiceDisconnected();
73 }
74 }
75
Santos Cordon493e8f22014-02-19 03:15:12 -080076 private static final String TAG = ServiceBinder.class.getSimpleName();
77
Santos Cordon63aeb162014-02-10 09:20:40 -080078 /** The application context. */
79 private final Context mContext;
80
81 /** The intent action to use when binding through {@link Context#bindService}. */
82 private final String mServiceAction;
83
84 /** The component name of the service to bind to. */
85 private final ComponentName mComponentName;
86
Santos Cordon5c12c6e2014-02-13 14:35:31 -080087 /** The set of callbacks waiting for notification of the binding's success or failure. */
88 private final Set<BindCallback> mCallbacks = Sets.newHashSet();
89
Santos Cordon63aeb162014-02-10 09:20:40 -080090 /** Used to bind and unbind from the service. */
91 private ServiceConnection mServiceConnection;
92
93 /** The binder provided by {@link ServiceConnection#onServiceConnected} */
94 private IBinder mBinder;
95
Ben Gilad8e55d1d2014-02-26 16:25:56 -080096 /** The number of calls currently associated with this service. */
97 private int mAssociatedCallCount = 0;
98
Santos Cordon63aeb162014-02-10 09:20:40 -080099 /**
100 * Indicates that an unbind request was made when the service was not yet bound. If the service
101 * successfully connects when this is true, it should be unbound immediately.
102 */
103 private boolean mIsBindingAborted;
104
105 /**
106 * Persists the specified parameters and initializes the new instance.
107 *
108 * @param serviceAction The intent-action used with {@link Context#bindService}.
109 * @param componentName The component name of the service with which to bind.
110 */
111 protected ServiceBinder(String serviceAction, ComponentName componentName) {
112 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
113 Preconditions.checkNotNull(componentName);
114
115 mContext = TelecommApp.getInstance();
116 mServiceAction = serviceAction;
117 mComponentName = componentName;
118 }
119
120 /**
121 * Performs an asynchronous bind to the service if not already bound.
122 *
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800123 * @param callback The callback to notify of the binding's success or failure.
Santos Cordon63aeb162014-02-10 09:20:40 -0800124 * @return The result of {#link Context#bindService} or true if already bound.
125 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800126 final boolean bind(BindCallback callback) {
Santos Cordon63aeb162014-02-10 09:20:40 -0800127 ThreadUtil.checkOnMainThread();
Santos Cordon493e8f22014-02-19 03:15:12 -0800128 Log.d(TAG, "bind()");
Santos Cordon63aeb162014-02-10 09:20:40 -0800129
130 // Reset any abort request if we're asked to bind again.
131 clearAbort();
132
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800133 // If we are already waiting on a binding request, simply append to the list of waiting
134 // callbacks.
135 if (!mCallbacks.isEmpty()) {
136 mCallbacks.add(callback);
137 return true;
138 }
139
Sailesh Nepalc3193752014-02-22 14:25:03 -0800140 mCallbacks.add(callback);
Santos Cordon63aeb162014-02-10 09:20:40 -0800141 if (mServiceConnection == null) {
142 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
143 ServiceConnection connection = new ServiceBinderConnection();
144
Santos Cordon493e8f22014-02-19 03:15:12 -0800145 Log.d(TAG, "Binding to call service with intent: " + serviceIntent);
Santos Cordon63aeb162014-02-10 09:20:40 -0800146 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
147 handleFailedConnection();
148 return false;
149 }
150 } else {
Santos Cordon493e8f22014-02-19 03:15:12 -0800151 Log.d(TAG, "Service is already bound.");
Santos Cordon63aeb162014-02-10 09:20:40 -0800152 Preconditions.checkNotNull(mBinder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800153 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800154 }
155
156 return true;
157 }
158
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800159 final void incrementAssociatedCallCount() {
160 mAssociatedCallCount++;
161 }
162
163 final void decrementAssociatedCallCount() {
164 if (mAssociatedCallCount > 0) {
165 mAssociatedCallCount--;
166 } else {
167 Log.wtf(TAG, mComponentName.getClassName() +
168 ": ignoring a request to decrement mAssociatedCallCount below zero");
169 }
170 }
171
172 final int getAssociatedCallCount() {
173 return mAssociatedCallCount;
174 }
175
Santos Cordon63aeb162014-02-10 09:20:40 -0800176 /**
177 * Unbinds from the service if already bound, no-op otherwise.
178 */
179 final void unbind() {
180 ThreadUtil.checkOnMainThread();
181
182 if (mServiceConnection == null) {
183 // We're not yet bound, so queue up an abort request.
184 mIsBindingAborted = true;
185 } else {
186 mContext.unbindService(mServiceConnection);
187 mServiceConnection = null;
188 mBinder = null;
189 }
190 }
191
Ben Giladbb167cd2014-02-25 16:24:05 -0800192 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800193 return mComponentName;
194 }
195
196 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800197 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
198 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800199 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800200 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800201 for (BindCallback callback : mCallbacks) {
202 callback.onSuccess();
203 }
204 mCallbacks.clear();
205 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800206
207 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800208 * Notifies all the outstanding callbacks that the service failed to bind. The list of
209 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800210 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800211 private void handleFailedConnection() {
212 for (BindCallback callback : mCallbacks) {
213 callback.onFailure();
214 }
215 mCallbacks.clear();
216 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800217
218 /**
219 * Handles a service disconnection.
220 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800221 private void handleServiceDisconnected() {
222 setServiceInterface(null);
223 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800224
225 private void clearAbort() {
226 mIsBindingAborted = false;
227 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800228
229 /**
230 * Sets the service interface after the service is bound or unbound.
231 *
232 * @param binder The actual bound service implementation.
233 */
234 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800235}