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