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