blob: 101b940cd54ae59790488d48c60ab005b9900ac9 [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
93 /**
94 * Indicates that an unbind request was made when the service was not yet bound. If the service
95 * successfully connects when this is true, it should be unbound immediately.
96 */
97 private boolean mIsBindingAborted;
98
99 /**
100 * Persists the specified parameters and initializes the new instance.
101 *
102 * @param serviceAction The intent-action used with {@link Context#bindService}.
103 * @param componentName The component name of the service with which to bind.
104 */
105 protected ServiceBinder(String serviceAction, ComponentName componentName) {
106 Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
107 Preconditions.checkNotNull(componentName);
108
109 mContext = TelecommApp.getInstance();
110 mServiceAction = serviceAction;
111 mComponentName = componentName;
112 }
113
114 /**
115 * Performs an asynchronous bind to the service if not already bound.
116 *
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800117 * @param callback The callback to notify of the binding's success or failure.
Santos Cordon63aeb162014-02-10 09:20:40 -0800118 * @return The result of {#link Context#bindService} or true if already bound.
119 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800120 final boolean bind(BindCallback callback) {
Santos Cordon63aeb162014-02-10 09:20:40 -0800121 ThreadUtil.checkOnMainThread();
122
123 // Reset any abort request if we're asked to bind again.
124 clearAbort();
125
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800126 // If we are already waiting on a binding request, simply append to the list of waiting
127 // callbacks.
128 if (!mCallbacks.isEmpty()) {
129 mCallbacks.add(callback);
130 return true;
131 }
132
Santos Cordon63aeb162014-02-10 09:20:40 -0800133 if (mServiceConnection == null) {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800134 mCallbacks.add(callback);
Santos Cordon63aeb162014-02-10 09:20:40 -0800135 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
136 ServiceConnection connection = new ServiceBinderConnection();
137
138 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
139 handleFailedConnection();
140 return false;
141 }
142 } else {
143 Preconditions.checkNotNull(mBinder);
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800144 handleSuccessfulConnection();
Santos Cordon63aeb162014-02-10 09:20:40 -0800145 }
146
147 return true;
148 }
149
150 /**
151 * Unbinds from the service if already bound, no-op otherwise.
152 */
153 final void unbind() {
154 ThreadUtil.checkOnMainThread();
155
156 if (mServiceConnection == null) {
157 // We're not yet bound, so queue up an abort request.
158 mIsBindingAborted = true;
159 } else {
160 mContext.unbindService(mServiceConnection);
161 mServiceConnection = null;
162 mBinder = null;
163 }
164 }
165
166 ComponentName getComponentName() {
167 return mComponentName;
168 }
169
170 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800171 * Notifies all the outstanding callbacks that the service is successfully bound. The list of
172 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800173 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -0800174 private void handleSuccessfulConnection() {
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800175 for (BindCallback callback : mCallbacks) {
176 callback.onSuccess();
177 }
178 mCallbacks.clear();
179 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800180
181 /**
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800182 * Notifies all the outstanding callbacks that the service failed to bind. The list of
183 * outstanding callbacks is cleared afterwards.
Santos Cordon63aeb162014-02-10 09:20:40 -0800184 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800185 private void handleFailedConnection() {
186 for (BindCallback callback : mCallbacks) {
187 callback.onFailure();
188 }
189 mCallbacks.clear();
190 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800191
192 /**
193 * Handles a service disconnection.
194 */
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800195 private void handleServiceDisconnected() {
196 setServiceInterface(null);
197 }
Santos Cordon63aeb162014-02-10 09:20:40 -0800198
199 private void clearAbort() {
200 mIsBindingAborted = false;
201 }
Santos Cordon5c12c6e2014-02-13 14:35:31 -0800202
203 /**
204 * Sets the service interface after the service is bound or unbound.
205 *
206 * @param binder The actual bound service implementation.
207 */
208 protected abstract void setServiceInterface(IBinder binder);
Santos Cordon63aeb162014-02-10 09:20:40 -0800209}