Home | History | Annotate | Download | only in telecom
      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 {
     47         void onSuccess();
     48         void onFailure();
     49     }
     50 
     51     /**
     52      * Listener for bind events on ServiceBinder.
     53      */
     54     interface Listener<ServiceBinderClass extends 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          */
     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 
    113         ServiceBinderConnection(Call call) {
    114             mCall = call;
    115         }
    116 
    117         @Override
    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
    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      */
    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 
    224     final void incrementAssociatedCallCount() {
    225         mAssociatedCallCount++;
    226         Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
    227                 mComponentName.flattenToShortString());
    228     }
    229 
    230     final void decrementAssociatedCallCount() {
    231         decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
    232     }
    233 
    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 
    249     final int getAssociatedCallCount() {
    250         return mAssociatedCallCount;
    251     }
    252 
    253     /**
    254      * Unbinds from the service if already bound, no-op otherwise.
    255      */
    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 
    268     final ComponentName getComponentName() {
    269         return mComponentName;
    270     }
    271 
    272     @VisibleForTesting
    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 
    282     final void addListener(Listener listener) {
    283         mListeners.add(listener);
    284     }
    285 
    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      */
    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      */
    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      */
    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      */
    329     private void handleServiceDisconnected() {
    330         setBinder(null);
    331     }
    332 
    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      */
    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      */
    362     protected abstract void setServiceInterface(IBinder binder);
    363 
    364     /**
    365      * Removes the service interface before the service is unbound.
    366      */
    367     protected abstract void removeServiceInterface();
    368 }
    369