blob: 3c8496788ac00d7023c01cd40217b4a9a43d8491 [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
96 /**
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();
Santos Cordon493e8f22014-02-19 03:15:12 -0800125 Log.d(TAG, "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
Santos Cordon493e8f22014-02-19 03:15:12 -0800142 Log.d(TAG, "Binding to call service with intent: " + 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 {
Santos Cordon493e8f22014-02-19 03:15:12 -0800148 Log.d(TAG, "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
156 /**
157 * Unbinds from the service if already bound, no-op otherwise.
158 */
159 final void unbind() {
160 ThreadUtil.checkOnMainThread();
161
162 if (mServiceConnection == null) {
163 // We're not yet bound, so queue up an abort request.
164 mIsBindingAborted = true;
165 } else {
166 mContext.unbindService(mServiceConnection);
167 mServiceConnection = null;
168 mBinder = null;
169 }
170 }
171
Ben Giladbb167cd2014-02-25 16:24:05 -0800172 final ComponentName getComponentName() {
Santos Cordon63aeb162014-02-10 09:20:40 -0800173 return mComponentName;
174 }
175
176 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800177 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
178 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800179 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800180 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800181 for (BindCallback callback : mCallbacks) {
182 callback.onSuccess();
183 }
184 mCallbacks.clear();
185 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800186
187 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800188 * Notifies all the outstanding callbacks that the service failed to bind. The list of
189 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800190 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800191 private void handleFailedConnection() {
192 for (BindCallback callback : mCallbacks) {
193 callback.onFailure();
194 }
195 mCallbacks.clear();
196 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800197
198 /**
199 * Handles a service disconnection.
200 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800201 private void handleServiceDisconnected() {
202 setServiceInterface(null);
203 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800204
205 private void clearAbort() {
206 mIsBindingAborted = false;
207 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800208
209 /**
210 * Sets the service interface after the service is bound or unbound.
211 *
212 * @param binder The actual bound service implementation.
213 */
214 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800215}