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 {
48         void onSuccess();
49         void onFailure();
50     }
51 
52     /**
53      * Listener for bind events on ServiceBinder.
54      */
55     interface Listener<ServiceBinderClass extends 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          */
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                 final boolean isBound;
92                 if (mUserHandle != null) {
93                     isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags,
94                             mUserHandle);
95                 } else {
96                     isBound = mContext.bindService(serviceIntent, connection, bindingFlags);
97                 }
98                 if (!isBound) {
99                     handleFailedConnection();
100                     return;
101                 }
102             } else {
103                 Log.d(ServiceBinder.this, "Service is already bound.");
104                 Preconditions.checkNotNull(mBinder);
105                 handleSuccessfulConnection();
106             }
107         }
108     }
109 
110     private class ServiceDeathRecipient implements IBinder.DeathRecipient {
111 
112         private ComponentName mComponentName;
113 
114         ServiceDeathRecipient(ComponentName name) {
115             mComponentName = name;
116         }
117 
118         @Override
119         public void binderDied() {
120             try {
121                 synchronized (mLock) {
122                     Log.startSession("SDR.bD",
123                             Log.getPackageAbbreviation(mComponentName));
124                     Log.i(this, "binderDied: ConnectionService %s died.", mComponentName);
125                     logServiceDisconnected("binderDied");
126                     handleDisconnect();
127                 }
128             } finally {
129                 Log.endSession();
130             }
131         }
132     }
133 
134     private final class ServiceBinderConnection implements ServiceConnection {
135         /**
136          * The initial call for which the service was bound.
137          */
138         private Call mCall;
139 
140         ServiceBinderConnection(Call call) {
141             mCall = call;
142         }
143 
144         @Override
145         public void onServiceConnected(ComponentName componentName, IBinder binder) {
146             try {
147                 Log.startSession("SBC.oSC", Log.getPackageAbbreviation(componentName));
148                 synchronized (mLock) {
149                     Log.i(this, "Service bound %s", componentName);
150 
151                     Log.addEvent(mCall, LogUtils.Events.CS_BOUND, componentName);
152                     mCall = null;
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
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     private void handleDisconnect() {
197         mServiceConnection = null;
198         clearAbort();
199 
200         handleServiceDisconnected();
201     }
202 
203     /** The application context. */
204     private final Context mContext;
205 
206     /** The Telecom lock object. */
207     protected final TelecomSystem.SyncRoot mLock;
208 
209     /** The intent action to use when binding through {@link Context#bindService}. */
210     private final String mServiceAction;
211 
212     /** The component name of the service to bind to. */
213     protected final ComponentName mComponentName;
214 
215     /**
216      * Abbreviated form of the package name from {@link #mComponentName}; used for session logging.
217      */
218     protected final String mPackageAbbreviation;
219 
220     /** The set of callbacks waiting for notification of the binding's success or failure. */
221     private final Set<BindCallback> mCallbacks = new ArraySet<>();
222 
223     /** Used to bind and unbind from the service. */
224     private ServiceConnection mServiceConnection;
225 
226     /** Used to handle death of the service. */
227     private ServiceDeathRecipient mServiceDeathRecipient;
228 
229     /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */
230     private UserHandle mUserHandle;
231 
232     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
233     private IBinder mBinder;
234 
235     private int mAssociatedCallCount = 0;
236 
237     /**
238      * Indicates that an unbind request was made when the service was not yet bound. If the service
239      * successfully connects when this is true, it should be unbound immediately.
240      */
241     private boolean mIsBindingAborted;
242 
243     /**
244      * Set of currently registered listeners.
245      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
246      * load factor before resizing, 1 means we only expect a single thread to
247      * access the map so make only a single shard
248      */
249     private final Set<Listener> mListeners = Collections.newSetFromMap(
250             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
251 
252     /**
253      * Persists the specified parameters and initializes the new instance.
254      *
255      * @param serviceAction The intent-action used with {@link Context#bindService}.
256      * @param componentName The component name of the service with which to bind.
257      * @param context The context.
258      * @param userHandle The {@link UserHandle} to use for binding.
259      */
260     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
261             TelecomSystem.SyncRoot lock, UserHandle userHandle) {
262         Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
263         Preconditions.checkNotNull(componentName);
264 
265         mContext = context;
266         mLock = lock;
267         mServiceAction = serviceAction;
268         mComponentName = componentName;
269         mPackageAbbreviation = Log.getPackageAbbreviation(componentName);
270         mUserHandle = userHandle;
271     }
272 
273     final UserHandle getUserHandle() {
274         return mUserHandle;
275     }
276 
277     final void incrementAssociatedCallCount() {
278         mAssociatedCallCount++;
279         Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
280                 mComponentName.flattenToShortString());
281     }
282 
283     final void decrementAssociatedCallCount() {
284         decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
285     }
286 
287     final void decrementAssociatedCallCount(boolean isSuppressingUnbind) {
288         if (mAssociatedCallCount > 0) {
289             mAssociatedCallCount--;
290             Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
291                     mComponentName.flattenToShortString());
292 
293             if (!isSuppressingUnbind && mAssociatedCallCount == 0) {
294                 unbind();
295             }
296         } else {
297             Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
298                     mComponentName.getClassName());
299         }
300     }
301 
302     final int getAssociatedCallCount() {
303         return mAssociatedCallCount;
304     }
305 
306     /**
307      * Unbinds from the service if already bound, no-op otherwise.
308      */
309     final void unbind() {
310         if (mServiceConnection == null) {
311             // We're not yet bound, so queue up an abort request.
312             mIsBindingAborted = true;
313         } else {
314             logServiceDisconnected("unbind");
315             unlinkDeathRecipient();
316             mContext.unbindService(mServiceConnection);
317             mServiceConnection = null;
318             setBinder(null);
319         }
320     }
321 
322     public final ComponentName getComponentName() {
323         return mComponentName;
324     }
325 
326     @VisibleForTesting
327     public boolean isServiceValid(String actionName) {
328         if (mBinder == null) {
329             Log.w(this, "%s invoked while service is unbound", actionName);
330             return false;
331         }
332 
333         return true;
334     }
335 
336     final void addListener(Listener listener) {
337         mListeners.add(listener);
338     }
339 
340     final void removeListener(Listener listener) {
341         if (listener != null) {
342             mListeners.remove(listener);
343         }
344     }
345 
346     /**
347      * Logs a standard message upon service disconnection. This method exists because there is no
348      * single method called whenever the service unbinds and we want to log the same string in all
349      * instances where that occurs.  (Context.unbindService() does not cause onServiceDisconnected
350      * to execute).
351      *
352      * @param sourceTag Tag to disambiguate
353      */
354     private void logServiceDisconnected(String sourceTag) {
355         Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
356     }
357 
358     /**
359      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
360      * outstanding callbacks is cleared afterwards.
361      */
362     private void handleSuccessfulConnection() {
363         // Make a copy so that we don't have a deadlock inside one of the callbacks.
364         Set<BindCallback> callbacksCopy = new ArraySet<>();
365         synchronized (mCallbacks) {
366             callbacksCopy.addAll(mCallbacks);
367             mCallbacks.clear();
368         }
369 
370         for (BindCallback callback : callbacksCopy) {
371             callback.onSuccess();
372         }
373     }
374 
375     /**
376      * Notifies all the outstanding callbacks that the service failed to bind. The list of
377      * outstanding callbacks is cleared afterwards.
378      */
379     private void handleFailedConnection() {
380         // Make a copy so that we don't have a deadlock inside one of the callbacks.
381         Set<BindCallback> callbacksCopy = new ArraySet<>();
382         synchronized (mCallbacks) {
383             callbacksCopy.addAll(mCallbacks);
384             mCallbacks.clear();
385         }
386 
387         for (BindCallback callback : callbacksCopy) {
388             callback.onFailure();
389         }
390     }
391 
392     /**
393      * Handles a service disconnection.
394      */
395     private void handleServiceDisconnected() {
396         unlinkDeathRecipient();
397         setBinder(null);
398     }
399 
400     /**
401      * Handles un-linking the death recipient from the service's binder.
402      */
403     private void unlinkDeathRecipient() {
404         if (mServiceDeathRecipient != null && mBinder != null) {
405             boolean unlinked = mBinder.unlinkToDeath(mServiceDeathRecipient, 0);
406             if (!unlinked) {
407                 Log.i(this, "unlinkDeathRecipient: failed to unlink %s", mComponentName);
408             }
409             mServiceDeathRecipient = null;
410         } else {
411             Log.w(this, "unlinkDeathRecipient: death recipient is null.");
412         }
413     }
414 
415     private void clearAbort() {
416         mIsBindingAborted = false;
417     }
418 
419     /**
420      * Sets the (private) binder and updates the child class.
421      *
422      * @param binder The new binder value.
423      */
424     private void setBinder(IBinder binder) {
425         if (mBinder != binder) {
426             if (binder == null) {
427                 removeServiceInterface();
428                 mBinder = null;
429                 for (Listener l : mListeners) {
430                     l.onUnbind(this);
431                 }
432             } else {
433                 mBinder = binder;
434                 setServiceInterface(binder);
435             }
436         }
437     }
438 
439     /**
440      * Sets the service interface after the service is bound.
441      *
442      * @param binder The new binder interface that is being set.
443      */
444     protected abstract void setServiceInterface(IBinder binder);
445 
446     /**
447      * Removes the service interface before the service is unbound.
448      */
449     protected abstract void removeServiceInterface();
450 }
451