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