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.IInterface;
     25 import android.os.Process;
     26 import android.os.UserHandle;
     27 import android.text.TextUtils;
     28 import android.util.ArraySet;
     29 
     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<ServiceInterface extends IInterface> {
     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 Binder {
     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          */
     68         void bind(BindCallback callback) {
     69             ThreadUtil.checkOnMainThread();
     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();
     85 
     86                 Log.d(ServiceBinder.this, "Binding to service with intent: %s", serviceIntent);
     87                 final boolean binding;
     88                 if (mUserHandle != null) {
     89                     binding = mContext.bindServiceAsUser(serviceIntent, connection,
     90                         Context.BIND_AUTO_CREATE, mUserHandle);
     91                 } else {
     92                     binding = mContext.bindService(serviceIntent, connection,
     93                         Context.BIND_AUTO_CREATE);
     94                 }
     95                 if (!binding) {
     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         @Override
    109         public void onServiceConnected(ComponentName componentName, IBinder binder) {
    110             ThreadUtil.checkOnMainThread();
    111             Log.i(this, "Service bound %s", componentName);
    112 
    113             // Unbind request was queued so unbind immediately.
    114             if (mIsBindingAborted) {
    115                 clearAbort();
    116                 logServiceDisconnected("onServiceConnected");
    117                 mContext.unbindService(this);
    118                 handleFailedConnection();
    119                 return;
    120             }
    121 
    122             mServiceConnection = this;
    123             setBinder(binder);
    124             handleSuccessfulConnection();
    125         }
    126 
    127         @Override
    128         public void onServiceDisconnected(ComponentName componentName) {
    129             logServiceDisconnected("onServiceDisconnected");
    130 
    131             mServiceConnection = null;
    132             clearAbort();
    133 
    134             handleServiceDisconnected();
    135         }
    136     }
    137 
    138     /** The application context. */
    139     private final Context mContext;
    140 
    141     /** The intent action to use when binding through {@link Context#bindService}. */
    142     private final String mServiceAction;
    143 
    144     /** The component name of the service to bind to. */
    145     private final ComponentName mComponentName;
    146 
    147     /** The set of callbacks waiting for notification of the binding's success or failure. */
    148     private final Set<BindCallback> mCallbacks = new ArraySet<>();
    149 
    150     /** Used to bind and unbind from the service. */
    151     private ServiceConnection mServiceConnection;
    152 
    153     /** {@link UserHandle} to use for binding, to support work profiles and multi-user. */
    154     private UserHandle mUserHandle;
    155 
    156     /** The binder provided by {@link ServiceConnection#onServiceConnected} */
    157     private IBinder mBinder;
    158 
    159     private int mAssociatedCallCount = 0;
    160 
    161     /**
    162      * Indicates that an unbind request was made when the service was not yet bound. If the service
    163      * successfully connects when this is true, it should be unbound immediately.
    164      */
    165     private boolean mIsBindingAborted;
    166 
    167     /**
    168      * Set of currently registered listeners.
    169      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
    170      * load factor before resizing, 1 means we only expect a single thread to
    171      * access the map so make only a single shard
    172      */
    173     private final Set<Listener> mListeners = Collections.newSetFromMap(
    174             new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
    175 
    176     /**
    177      * Persists the specified parameters and initializes the new instance.
    178      *
    179      * @param serviceAction The intent-action used with {@link Context#bindService}.
    180      * @param componentName The component name of the service with which to bind.
    181      * @param context The context.
    182      * @param userHandle The {@link UserHandle} to use for binding.
    183      */
    184     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
    185             UserHandle userHandle) {
    186         Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
    187         Preconditions.checkNotNull(componentName);
    188 
    189         mContext = context;
    190         mServiceAction = serviceAction;
    191         mComponentName = componentName;
    192         mUserHandle = userHandle;
    193     }
    194 
    195     final void incrementAssociatedCallCount() {
    196         mAssociatedCallCount++;
    197         Log.v(this, "Call count increment %d, %s", mAssociatedCallCount,
    198                 mComponentName.flattenToShortString());
    199     }
    200 
    201     final void decrementAssociatedCallCount() {
    202         if (mAssociatedCallCount > 0) {
    203             mAssociatedCallCount--;
    204             Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
    205                     mComponentName.flattenToShortString());
    206 
    207             if (mAssociatedCallCount == 0) {
    208                 unbind();
    209             }
    210         } else {
    211             Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
    212                     mComponentName.getClassName());
    213         }
    214     }
    215 
    216     final int getAssociatedCallCount() {
    217         return mAssociatedCallCount;
    218     }
    219 
    220     /**
    221      * Unbinds from the service if already bound, no-op otherwise.
    222      */
    223     final void unbind() {
    224         ThreadUtil.checkOnMainThread();
    225 
    226         if (mServiceConnection == null) {
    227             // We're not yet bound, so queue up an abort request.
    228             mIsBindingAborted = true;
    229         } else {
    230             logServiceDisconnected("unbind");
    231             mContext.unbindService(mServiceConnection);
    232             mServiceConnection = null;
    233             setBinder(null);
    234         }
    235     }
    236 
    237     final ComponentName getComponentName() {
    238         return mComponentName;
    239     }
    240 
    241     final boolean isServiceValid(String actionName) {
    242         if (mBinder == null) {
    243             Log.w(this, "%s invoked while service is unbound", actionName);
    244             return false;
    245         }
    246 
    247         return true;
    248     }
    249 
    250     final void addListener(Listener listener) {
    251         mListeners.add(listener);
    252     }
    253 
    254     final void removeListener(Listener listener) {
    255         if (listener != null) {
    256             mListeners.remove(listener);
    257         }
    258     }
    259 
    260     /**
    261      * Logs a standard message upon service disconnection. This method exists because there is no
    262      * single method called whenever the service unbinds and we want to log the same string in all
    263      * instances where that occurs.  (Context.unbindService() does not cause onServiceDisconnected
    264      * to execute).
    265      *
    266      * @param sourceTag Tag to disambiguate
    267      */
    268     private void logServiceDisconnected(String sourceTag) {
    269         Log.i(this, "Service unbound %s, from %s.", mComponentName, sourceTag);
    270     }
    271 
    272     /**
    273      * Notifies all the outstanding callbacks that the service is successfully bound. The list of
    274      * outstanding callbacks is cleared afterwards.
    275      */
    276     private void handleSuccessfulConnection() {
    277         for (BindCallback callback : mCallbacks) {
    278             callback.onSuccess();
    279         }
    280         mCallbacks.clear();
    281     }
    282 
    283     /**
    284      * Notifies all the outstanding callbacks that the service failed to bind. The list of
    285      * outstanding callbacks is cleared afterwards.
    286      */
    287     private void handleFailedConnection() {
    288         for (BindCallback callback : mCallbacks) {
    289             callback.onFailure();
    290         }
    291         mCallbacks.clear();
    292     }
    293 
    294     /**
    295      * Handles a service disconnection.
    296      */
    297     private void handleServiceDisconnected() {
    298         setBinder(null);
    299     }
    300 
    301     private void clearAbort() {
    302         mIsBindingAborted = false;
    303     }
    304 
    305     /**
    306      * Sets the (private) binder and updates the child class.
    307      *
    308      * @param binder The new binder value.
    309      */
    310     private void setBinder(IBinder binder) {
    311         if (mBinder != binder) {
    312             mBinder = binder;
    313 
    314             setServiceInterface(binder);
    315 
    316             if (binder == null) {
    317                 for (Listener l : mListeners) {
    318                     l.onUnbind(this);
    319                 }
    320             }
    321         }
    322     }
    323 
    324     /**
    325      * Sets the service interface after the service is bound or unbound.
    326      *
    327      * @param binder The actual bound service implementation.
    328      */
    329     protected abstract void setServiceInterface(IBinder binder);
    330 }
    331