• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.telecom;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.IBinder;
24 import android.os.IInterface;
25 
26 import com.android.internal.util.Preconditions;
27 import com.google.common.base.Strings;
28 
29 import com.google.common.collect.Sets;
30 
31 import java.util.Collections;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 
35 /**
36  * Abstract class to perform the work of binding and unbinding to the specified service interface.
37  * Subclasses supply the service intent and component name and this class will invoke protected
38  * methods when the class is bound, unbound, or upon failure.
39  */
40 abstract class ServiceBinder<ServiceInterface extends IInterface> {
41 
42     /**
43      * Callback to notify after a binding succeeds or fails.
44      */
45     interface BindCallback {
onSuccess()46         void onSuccess();
onFailure()47         void onFailure();
48     }
49 
50     /**
51      * Listener for bind events on ServiceBinder.
52      */
53     interface Listener<ServiceBinderClass extends ServiceBinder<?>> {
onUnbind(ServiceBinderClass serviceBinder)54         void onUnbind(ServiceBinderClass serviceBinder);
55     }
56 
57     /**
58      * Helper class to perform on-demand binding.
59      */
60     final class Binder {
61         /**
62          * Performs an asynchronous bind to the service (only if not already bound) and executes the
63          * specified callback.
64          *
65          * @param callback The callback to notify of the binding's success or failure.
66          */
bind(BindCallback callback)67         void bind(BindCallback callback) {
68             ThreadUtil.checkOnMainThread();
69             Log.d(ServiceBinder.this, "bind()");
70 
71             // Reset any abort request if we're asked to bind again.
72             clearAbort();
73 
74             if (!mCallbacks.isEmpty()) {
75                 // Binding already in progress, append to the list of callbacks and bail out.
76                 mCallbacks.add(callback);
77                 return;
78             }
79 
80             mCallbacks.add(callback);
81             if (mServiceConnection == null) {
82                 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
83                 ServiceConnection connection = new ServiceBinderConnection();
84 
85                 Log.d(ServiceBinder.this, "Binding to service with intent: %s", serviceIntent);
86                 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
87                     handleFailedConnection();
88                     return;
89                 }
90             } else {
91                 Log.d(ServiceBinder.this, "Service is already bound.");
92                 Preconditions.checkNotNull(mBinder);
93                 handleSuccessfulConnection();
94             }
95         }
96     }
97 
98     private final class ServiceBinderConnection implements ServiceConnection {
99         @Override
onServiceConnected(ComponentName componentName, IBinder binder)100         public void onServiceConnected(ComponentName componentName, IBinder binder) {
101             ThreadUtil.checkOnMainThread();
102             Log.i(this, "Service bound %s", componentName);
103 
104             // Unbind request was queued so unbind immediately.
105             if (mIsBindingAborted) {
106                 clearAbort();
107                 logServiceDisconnected("onServiceConnected");
108                 mContext.unbindService(this);
109                 handleFailedConnection();
110                 return;
111             }
112 
113             mServiceConnection = this;
114             setBinder(binder);
115             handleSuccessfulConnection();
116         }
117 
118         @Override
onServiceDisconnected(ComponentName componentName)119         public void onServiceDisconnected(ComponentName componentName) {
120             logServiceDisconnected("onServiceDisconnected");
121 
122             mServiceConnection = null;
123             clearAbort();
124 
125             handleServiceDisconnected();
126         }
127     }
128 
129     /** The application context. */
130     private final Context mContext;
131 
132     /** The intent action to use when binding through {@link Context#bindService}. */
133     private final String mServiceAction;
134 
135     /** The component name of the service to bind to. */
136     private final ComponentName mComponentName;
137 
138     /** The set of callbacks waiting for notification of the binding's success or failure. */
139     private final Set<BindCallback> mCallbacks = Sets.newHashSet();
140 
141     /** Used to bind and unbind from the service. */
142     private ServiceConnection mServiceConnection;
143 
144     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
145     private IBinder mBinder;
146 
147     private int mAssociatedCallCount = 0;
148 
149     /**
150      * Indicates that an unbind request was made when the service was not yet bound. If the service
151      * successfully connects when this is true, it should be unbound immediately.
152      */
153     private boolean mIsBindingAborted;
154 
155     /**
156      * Set of currently registered listeners.
157      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
158      * load factor before resizing, 1 means we only expect a single thread to
159      * access the map so make only a single shard
160      */
161     private final Set<Listener> mListeners = Collections.newSetFromMap(
162             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
163 
164     /**
165      * Persists the specified parameters and initializes the new instance.
166      *
167      * @param serviceAction The intent-action used with {@link Context#bindService}.
168      * @param componentName The component name of the service with which to bind.
169      * @param context The context.
170      */
ServiceBinder(String serviceAction, ComponentName componentName, Context context)171     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context) {
172         Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
173         Preconditions.checkNotNull(componentName);
174 
175         mContext = context;
176         mServiceAction = serviceAction;
177         mComponentName = componentName;
178     }
179 
incrementAssociatedCallCount()180     final void incrementAssociatedCallCount() {
181         mAssociatedCallCount++;
182         Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
183                 mComponentName.flattenToShortString());
184     }
185 
decrementAssociatedCallCount()186     final void decrementAssociatedCallCount() {
187         if (mAssociatedCallCount > 0) {
188             mAssociatedCallCount--;
189             Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
190                     mComponentName.flattenToShortString());
191 
192             if (mAssociatedCallCount == 0) {
193                 unbind();
194             }
195         } else {
196             Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
197                     mComponentName.getClassName());
198         }
199     }
200 
getAssociatedCallCount()201     final int getAssociatedCallCount() {
202         return mAssociatedCallCount;
203     }
204 
205     /**
206      * Unbinds from the service if already bound, no-op otherwise.
207      */
unbind()208     final void unbind() {
209         ThreadUtil.checkOnMainThread();
210 
211         if (mServiceConnection == null) {
212             // We're not yet bound, so queue up an abort request.
213             mIsBindingAborted = true;
214         } else {
215             logServiceDisconnected("unbind");
216             mContext.unbindService(mServiceConnection);
217             mServiceConnection = null;
218             setBinder(null);
219         }
220     }
221 
getComponentName()222     final ComponentName getComponentName() {
223         return mComponentName;
224     }
225 
isServiceValid(String actionName)226     final boolean isServiceValid(String actionName) {
227         if (mBinder == null) {
228             Log.w(this, "%s invoked while service is unbound", actionName);
229             return false;
230         }
231 
232         return true;
233     }
234 
addListener(Listener listener)235     final void addListener(Listener listener) {
236         mListeners.add(listener);
237     }
238 
removeListener(Listener listener)239     final void removeListener(Listener listener) {
240         if (listener != null) {
241             mListeners.remove(listener);
242         }
243     }
244 
245     /**
246      * Logs a standard message upon service disconnection. This method exists because there is no
247      * single method called whenever the service unbinds and we want to log the same string in all
248      * instances where that occurs.  (Context.unbindService() does not cause onServiceDisconnected
249      * to execute).
250      *
251      * @param sourceTag Tag to disambiguate
252      */
logServiceDisconnected(String sourceTag)253     private void logServiceDisconnected(String sourceTag) {
254         Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
255     }
256 
257     /**
258      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
259      * outstanding callbacks is cleared afterwards.
260      */
handleSuccessfulConnection()261     private void handleSuccessfulConnection() {
262         for (BindCallback callback : mCallbacks) {
263             callback.onSuccess();
264         }
265         mCallbacks.clear();
266     }
267 
268     /**
269      * Notifies all the outstanding callbacks that the service failed to bind. The list of
270      * outstanding callbacks is cleared afterwards.
271      */
handleFailedConnection()272     private void handleFailedConnection() {
273         for (BindCallback callback : mCallbacks) {
274             callback.onFailure();
275         }
276         mCallbacks.clear();
277     }
278 
279     /**
280      * Handles a service disconnection.
281      */
handleServiceDisconnected()282     private void handleServiceDisconnected() {
283         setBinder(null);
284     }
285 
clearAbort()286     private void clearAbort() {
287         mIsBindingAborted = false;
288     }
289 
290     /**
291      * Sets the (private) binder and updates the child class.
292      *
293      * @param binder The new binder value.
294      */
setBinder(IBinder binder)295     private void setBinder(IBinder binder) {
296         if (mBinder != binder) {
297             mBinder = binder;
298 
299             setServiceInterface(binder);
300 
301             if (binder == null) {
302                 for (Listener l : mListeners) {
303                     l.onUnbind(this);
304                 }
305             }
306         }
307     }
308 
309     /**
310      * Sets the service interface after the service is bound or unbound.
311      *
312      * @param binder The actual bound service implementation.
313      */
setServiceInterface(IBinder binder)314     protected abstract void setServiceInterface(IBinder binder);
315 }
316