Adding a CallService & CallServiceProvider concrete classes.

First step in no longer using ICallService and ICallServiceProviders
directly.
Adds a generic ServiceBinder class to manage binding/unbinding.

Change-Id: I7d26958dd85a99316dbd5e70caba3fd91d35911b
diff --git a/src/com/android/telecomm/CallService.java b/src/com/android/telecomm/CallService.java
deleted file mode 100644
index 72b73a7..0000000
--- a/src/com/android/telecomm/CallService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.android.telecomm;
-
-import com.android.telecomm.exceptions.OutgoingCallException;
-
-// TODO(gilad): Move to use the AIDL-based implementation.
-public interface CallService {
-
-  public void setCallServiceAdapter(CallServiceAdapter adapter);
-
-  public boolean isCompatibleWith(String userInput, ContactInfo contactInfo);
-
-  public void placeOutgoingCall(String userInput, ContactInfo contactInfo)
-    throws OutgoingCallException;
-}
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index bfbdebd..ccfb2cc 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -31,6 +31,7 @@
  * check that the invocation is expected from that call service.
  * TODO(santoscordon): Move away from Runnable objects and into messages so that we create fewer
  * objects per IPC method call.
+ * TODO(santoscordon): Do we need Binder.clear/restoreCallingIdentity() in the service methods?
  */
 public final class CallServiceAdapter extends ICallServiceAdapter.Stub {
 
diff --git a/src/com/android/telecomm/CallServiceFinder.java b/src/com/android/telecomm/CallServiceFinder.java
index 7fc187b..d24c612 100644
--- a/src/com/android/telecomm/CallServiceFinder.java
+++ b/src/com/android/telecomm/CallServiceFinder.java
@@ -17,9 +17,7 @@
 package com.android.telecomm;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -34,9 +32,11 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -56,12 +56,6 @@
      */
     private static final int LOOKUP_TIMEOUT_MS = 100;
 
-    /**
-     * Used to retrieve all known ICallServiceProvider implementations from the framework.
-     * TODO(gilad): Move to a more logical place for this to be shared.
-     */
-    static final String CALL_SERVICE_PROVIDER_CLASS_NAME = ICallServiceProvider.class.getName();
-
     private final Switchboard mSwitchboard;
 
     private final OutgoingCallsManager mOutgoingCallsManager;
@@ -72,11 +66,11 @@
     private boolean mIsLookupInProgress = false;
 
     /**
-     * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
+     * The current lookup-cycle ID. Incremented upon initiateLookup calls.
      * TODO(gilad): If at all useful, consider porting the cycle ID concept to switchboard and
      * have it centralized/shared between the two finders.
      */
-    private int mNextLookupId = 0;
+    private int mLookupId = 0;
 
     /**
      * The set of bound call-services. Only populated via initiateLookup scenarios. Entries should
@@ -88,18 +82,25 @@
     /**
      * The set of bound call-service providers.  Only populated via initiateLookup scenarios.
      * Providers should only be removed upon unbinding.
+     * TODO(santoscordon): This can be removed once this class starts using CallServiceWrapper
+     * since we'll be able to unbind the providers within registerProvider().
      */
-    private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
+    private Set<CallServiceProviderWrapper> mProviderRegistry = Sets.newHashSet();
+
+    /**
+     * Map of {@link CallServiceProviderWrapper}s keyed by their ComponentName. Used as a long-lived
+     * cache in order to simplify management of service-wrapper construction/destruction.
+     */
+    private Map<ComponentName, CallServiceProviderWrapper> mProviderCache = Maps.newHashMap();
 
     /**
      * Stores the names of the providers to bind to in one lookup cycle.  The set size represents
      * the number of call-service providers this finder expects to hear back from upon initiating
      * call-service lookups, see initiateLookup. Whenever all providers respond before the lookup
      * timeout occurs, the complete set of (available) call services is passed to the switchboard
-     * for further processing of outgoing calls etc.  When the timeout occurs before all responds
+     * for further processing of outgoing calls etc.  When the timeout occurs before all responses
      * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
-     * Cached providers do not require finding and hence are excluded from this set.  Entries are
-     * removed from this set as providers register.
+     * Entries are removed from this set as providers register.
      */
     private Set<ComponentName> mUnregisteredProviders;
 
@@ -131,34 +132,31 @@
     /**
      * Initiates a lookup cycle for call-service providers. Must be called from the UI thread.
      * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
-     *
-     * @param context The relevant application context.
      */
-    void initiateLookup(Context context) {
+    void initiateLookup() {
         ThreadUtil.checkOnMainThread();
         if (mIsLookupInProgress) {
             // At most one active lookup is allowed at any given time, bail out.
             return;
         }
 
-        List<ComponentName> providerNames = getProviderNames(context);
+        List<ComponentName> providerNames = getProviderNames();
         if (providerNames.isEmpty()) {
             Log.i(TAG, "No ICallServiceProvider implementations found.");
             updateSwitchboard();
             return;
         }
 
+        mLookupId++;
         mIsLookupInProgress = true;
         mUnregisteredProviders = Sets.newHashSet();
 
-        int lookupId = mNextLookupId++;
         for (ComponentName name : providerNames) {
-            if (!mProviderRegistry.contains(name)) {
-                // The provider is either not yet registered or has been unregistered
-                // due to unbinding etc.
-                bindProvider(name, lookupId, context);
-                mUnregisteredProviders.add(name);
-            }
+            // Bind to each of the providers that were found. Some of the providers may already be
+            // bound, and in those cases the provider wrapper will still invoke registerProvider()
+            // allowing us to treat bound and unbound providers the same.
+            getProvider(name).bind();
+            mUnregisteredProviders.add(name);
         }
 
         int providerCount = providerNames.size();
@@ -181,16 +179,15 @@
     /**
      * Returns the all-inclusive list of call-service-provider names.
      *
-     * @param context The relevant/application context to query against.
      * @return The list containing the (component) names of all known ICallServiceProvider
      *     implementations or the empty list upon no available providers.
      */
-    private List<ComponentName> getProviderNames(Context context) {
+    private List<ComponentName> getProviderNames() {
         // The list of provider names to return to the caller, may be populated below.
         List<ComponentName> providerNames = Lists.newArrayList();
 
-        PackageManager packageManager = context.getPackageManager();
-        Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
+        PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
+        Intent intent = new Intent(CallServiceProviderWrapper.CALL_SERVICE_PROVIDER_ACTION);
         for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
             ServiceInfo serviceInfo = entry.serviceInfo;
             if (serviceInfo != null) {
@@ -204,74 +201,31 @@
     }
 
     /**
-     * Attempts to bind the specified provider and have it register upon successful binding.  Also
-     * performs the necessary wiring to unregister the provider upon un-binding.
-     *
-     * @param providerName The component name of the relevant provider.
-     * @param lookupId The lookup-cycle ID.
-     * @param context The relevant application context.
-     */
-    private void bindProvider(
-            final ComponentName providerName, final int lookupId, Context context) {
-
-        Preconditions.checkNotNull(providerName);
-        Preconditions.checkNotNull(context);
-
-        Intent serviceIntent =
-                new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(providerName);
-        Log.i(TAG, "Binding to ICallServiceProvider through " + serviceIntent);
-
-        // Connection object for the service binding.
-        ServiceConnection connection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                ICallServiceProvider provider = ICallServiceProvider.Stub.asInterface(service);
-                registerProvider(lookupId, providerName, provider);
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName className) {
-                unregisterProvider(providerName);
-            }
-        };
-
-        if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
-            // TODO(santoscordon): Handle error.
-        }
-    }
-
-    /**
      * Queries the supplied provider asynchronously for its CallServices and passes the list through
      * to {@link #registerCallServices} which will relinquish control back to switchboard.
      *
-     * @param lookupId The lookup-cycle ID.
      * @param providerName The component name of the relevant provider.
      * @param provider The provider object to register.
      */
-    private void registerProvider(
-            final int lookupId,
-            final ComponentName providerName,
-            final ICallServiceProvider provider) {
+    void registerProvider(
+            final ComponentName providerName, final CallServiceProviderWrapper provider) {
 
         // Query the provider for {@link ICallService} implementations.
-        try {
-            provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
-                @Override
-                public void setCallServices(final List<IBinder> binderList) {
-                    mHandler.post(new Runnable() {
-                        @Override public void run() {
-                            Set<ICallService> callServices = Sets.newHashSet();
-                            for (IBinder binder : binderList) {
-                                callServices.add(ICallService.Stub.asInterface(binder));
-                            }
-                            registerCallServices(lookupId, providerName, provider, callServices);
+        provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
+            @Override
+            public void setCallServices(final List<IBinder> binderList) {
+                // TODO(santoscordon): Do we need Binder.clear/restoreCallingIdentity()?
+                mHandler.post(new Runnable() {
+                    @Override public void run() {
+                        Set<ICallService> callServices = Sets.newHashSet();
+                        for (IBinder binder : binderList) {
+                            callServices.add(ICallService.Stub.asInterface(binder));
                         }
-                    });
-                }
-            });
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not retrieve call services from: " + providerName);
-        }
+                        registerCallServices(providerName, provider, callServices);
+                    }
+                });
+            }
+        });
     }
 
     /**
@@ -279,24 +233,20 @@
      * bookkeeping to potentially return control to the switchboard before the timeout for the
      * current lookup cycle.
      *
-     * @param lookupId The lookup-cycle ID.
      * @param providerName The component name of the relevant provider.
      * @param provider The provider associated with callServices.
      * @param callServices The {@link CallService}s to register.
      */
     private void registerCallServices(
-            int lookupId,
             ComponentName providerName,
-            ICallServiceProvider provider,
+            CallServiceProviderWrapper provider,
             Set<ICallService> callServices) {
         ThreadUtil.checkOnMainThread();
 
-        // TODO(santoscordon): When saving the call services into this class, also add code to
-        // unregister (remove) the call services upon disconnect. Potentially use
-        // RemoteCallbackList.
-
         if (mUnregisteredProviders.remove(providerName)) {
             mProviderRegistry.add(provider);
+
+            // Add all the call services from this provider to the call-service registry.
             for (ICallService callService : callServices) {
                 try {
                     CallServiceAdapter adapter = new CallServiceAdapter(mOutgoingCallsManager);
@@ -307,16 +257,12 @@
                 }
             }
 
-            // TODO(gilad): Introduce a map to retain the association between call services
-            // and the corresponding provider such that mCallServiceRegistry can be updated
-            // upon unregisterProvider calls.
-
             if (mUnregisteredProviders.size() < 1) {
                 terminateLookup();  // No other providers to wait for.
             }
         } else {
-            Log.i(TAG, "Received multiple lists of call services in lookup " + lookupId +
-                    " from " + providerName);
+            Log.i(TAG, "Unexpected list of call services in lookup " + mLookupId + " from " +
+                    providerName);
         }
     }
 
@@ -325,7 +271,7 @@
      *
      * @param providerName The component name of the relevant provider.
      */
-    private void unregisterProvider(ComponentName providerName) {
+    void unregisterProvider(ComponentName providerName) {
         ThreadUtil.checkOnMainThread();
         mProviderRegistry.remove(providerName);
     }
@@ -335,6 +281,7 @@
      */
     private void terminateLookup() {
         mHandler.removeCallbacks(mLookupTerminator);
+        mUnregisteredProviders.clear();
 
         updateSwitchboard();
         mIsLookupInProgress = false;
@@ -348,4 +295,22 @@
         ThreadUtil.checkOnMainThread();
         mSwitchboard.setCallServices(mCallServiceRegistry);
     }
+
+    /**
+     * Returns the call-service provider wrapper for the specified componentName. Creates a new one
+     * if none is found in the cache.
+     *
+     * @param ComponentName The component name of the provider.
+     */
+    private CallServiceProviderWrapper getProvider(ComponentName componentName) {
+        Preconditions.checkNotNull(componentName);
+
+        CallServiceProviderWrapper provider = mProviderCache.get(componentName);
+        if (provider == null) {
+            provider = new CallServiceProviderWrapper(componentName, this);
+            mProviderCache.put(componentName, provider);
+        }
+
+        return provider;
+    }
 }
diff --git a/src/com/android/telecomm/CallServiceProviderWrapper.java b/src/com/android/telecomm/CallServiceProviderWrapper.java
new file mode 100644
index 0000000..044d370
--- /dev/null
+++ b/src/com/android/telecomm/CallServiceProviderWrapper.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecomm;
+
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecomm.ICallServiceLookupResponse;
+import android.telecomm.ICallServiceProvider;
+import android.util.Log;
+
+/**
+ * Wrapper for {@link ICallServiceProvider}s, handles binding to {@link ICallServiceProvider} and
+ * keeps track of when the object can safely be unbound. Other classes should not use
+ * {@link ICallServiceProvider} directly and instead should use this class to invoke methods of
+ * {@link ICallServiceProvider}.
+ * TODO(santoscordon): Keep track of when the service can be safely unbound.
+ * TODO(santoscordon): Look into combining with android.telecomm.CallServiceProvider.
+ */
+public class CallServiceProviderWrapper extends ServiceBinder<ICallServiceProvider> {
+    /**
+     * The service action used to bind to ICallServiceProvider implementations.
+     * TODO(santoscordon): Move this to TelecommConstants.
+     */
+    static final String CALL_SERVICE_PROVIDER_ACTION = ICallServiceProvider.class.getName();
+
+    private static final String TAG = CallServiceProviderWrapper.class.getSimpleName();
+
+    /** The actual service implementation. */
+    private ICallServiceProvider mServiceInterface;
+
+    /**
+     * The class to notify when the binding succeeds or fails.
+     */
+    private final CallServiceFinder mFinder;
+
+    /**
+     * Creates a call-service provider for the specified component.
+     */
+    public CallServiceProviderWrapper(ComponentName componentName, CallServiceFinder finder) {
+        super(CALL_SERVICE_PROVIDER_ACTION, componentName);
+        mFinder = finder;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleSuccessfulConnection(IBinder binder) {
+        mServiceInterface = ICallServiceProvider.Stub.asInterface(binder);
+        mFinder.registerProvider(getComponentName(), this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleFailedConnection() {
+        mFinder.unregisterProvider(getComponentName());
+        // TODO(santoscordon): Notify CallServiceFinder.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleServiceDisconnected() {
+        mServiceInterface = null;
+        // TODO(santoscordon): fill in.
+    }
+
+    /** See {@link ICallServiceProvider#lookupCallServices}. */
+    public void lookupCallServices(ICallServiceLookupResponse response) {
+        try {
+            if (mServiceInterface == null) {
+                Log.wtf(TAG, "lookupCallServices() invoked while the service is unbound.");
+            } else {
+                mServiceInterface.lookupCallServices(response);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to lookupCallServices.", e);
+        }
+    }
+}
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
new file mode 100644
index 0000000..c7b494f
--- /dev/null
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecomm;
+
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecomm.CallInfo;
+import android.telecomm.ICallService;
+import android.telecomm.ICallServiceAdapter;
+import android.util.Log;
+
+/**
+ * Wrapper for {@link ICallService}s, handles binding to {@link ICallService} and keeps track of
+ * when the object can safely be unbound. Other classes should not use {@link ICallService} directly
+ * and instead should use this class to invoke methods of {@link ICallService}.
+ * TODO(santoscordon): Keep track of when the service can be safely unbound.
+ * TODO(santoscordon): Look into combining with android.telecomm.CallService.
+ */
+public class CallServiceWrapper extends ServiceBinder<ICallService> {
+    private static final String TAG = CallServiceWrapper.class.getSimpleName();
+
+    /** The actual service implementation. */
+    private ICallService mServiceInterface;
+
+    /**
+     * The service action used to bind to ICallService implementations.
+     * TODO(santoscordon): Move this to TelecommConstants.
+     */
+    static final String CALL_SERVICE_ACTION = ICallService.class.getName();
+
+    /**
+     * Creates a call-service provider for the specified component.
+     */
+    public CallServiceWrapper(ComponentName componentName) {
+        super(CALL_SERVICE_ACTION, componentName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleSuccessfulConnection(IBinder binder) {
+        mServiceInterface = ICallService.Stub.asInterface(binder);
+        // TODO(santoscordon): Notify CallServiceFinder.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleFailedConnection() {
+        // TODO(santoscordon): Notify CallServiceFinder.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void handleServiceDisconnected() {
+        mServiceInterface = null;
+        // TODO(santoscordon): fill in.
+    }
+
+    /** See {@link ICallService#setCallServiceAdapter}. */
+    public void setCallServiceAdapter(ICallServiceAdapter callServiceAdapter) {
+        try {
+            if (mServiceInterface == null) {
+                Log.wtf(TAG, "setCallServiceAdapter() invoked while the service is unbound.");
+            } else {
+                mServiceInterface.setCallServiceAdapter(callServiceAdapter);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to setCallServiceAdapter.", e);
+        }
+    }
+
+    /** See {@link ICallService#isCompatibleWith}. */
+    public void isCompatibleWith(CallInfo callInfo) {
+        try {
+            if (mServiceInterface == null) {
+                Log.wtf(TAG, "isCompatibleWith() invoked while the service is unbound.");
+            } else {
+                mServiceInterface.isCompatibleWith(callInfo);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed checking isCompatibleWith.", e);
+        }
+    }
+
+    /** See {@link ICallService#call}. */
+    public void call(CallInfo callInfo) {
+        try {
+            if (mServiceInterface == null) {
+                Log.wtf(TAG, "call() invoked while the service is unbound.");
+            } else {
+                mServiceInterface.call(callInfo);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to place call " + callInfo.getId() + ".", e);
+        }
+    }
+
+    /** See {@link ICallService#disconnect}. */
+    public void disconnect(String callId) {
+        try {
+            if (mServiceInterface == null) {
+                Log.wtf(TAG, "disconnect() invoked while the service is unbound.");
+            } else {
+                mServiceInterface.disconnect(callId);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to disconnect call " + callId + ".", e);
+        }
+    }
+}
diff --git a/src/com/android/telecomm/ServiceBinder.java b/src/com/android/telecomm/ServiceBinder.java
new file mode 100644
index 0000000..900d18f
--- /dev/null
+++ b/src/com/android/telecomm/ServiceBinder.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecomm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.IInterface;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+
+/**
+ * Abstract class to perform the work of binding and unbinding to the specified service interface.
+ * Subclasses supply the service intent and component name and this class will invoke protected
+ * methods when the class is bound, unbound, or upon failure.
+ */
+abstract class ServiceBinder<ServiceInterface extends IInterface> {
+
+    private final class ServiceBinderConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder binder) {
+            ThreadUtil.checkOnMainThread();
+
+            // Unbind request was queued so unbind immediately.
+            if (mIsBindingAborted) {
+                clearAbort();
+                mContext.unbindService(this);
+                return;
+            }
+
+            mServiceConnection = this;
+            mBinder = binder;
+            handleSuccessfulConnection(binder);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            mServiceConnection = null;
+            clearAbort();
+
+            handleServiceDisconnected();
+        }
+    }
+
+    /** The application context. */
+    private final Context mContext;
+
+    /** The intent action to use when binding through {@link Context#bindService}. */
+    private final String mServiceAction;
+
+    /** The component name of the service to bind to. */
+    private final ComponentName mComponentName;
+
+    /** Used to bind and unbind from the service. */
+    private ServiceConnection mServiceConnection;
+
+    /** The binder provided by {@link ServiceConnection#onServiceConnected} */
+    private IBinder mBinder;
+
+    /**
+     * Indicates that an unbind request was made when the service was not yet bound. If the service
+     * successfully connects when this is true, it should be unbound immediately.
+     */
+    private boolean mIsBindingAborted;
+
+    /**
+     * Persists the specified parameters and initializes the new instance.
+     *
+     * @param serviceAction The intent-action used with {@link Context#bindService}.
+     * @param componentName The component name of the service with which to bind.
+     */
+    protected ServiceBinder(String serviceAction, ComponentName componentName) {
+        Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
+        Preconditions.checkNotNull(componentName);
+
+        mContext = TelecommApp.getInstance();
+        mServiceAction = serviceAction;
+        mComponentName = componentName;
+    }
+
+    /**
+     * Performs an asynchronous bind to the service if not already bound.
+     *
+     * @return The result of {#link Context#bindService} or true if already bound.
+     */
+    final boolean bind() {
+        ThreadUtil.checkOnMainThread();
+
+        // Reset any abort request if we're asked to bind again.
+        clearAbort();
+
+        if (mServiceConnection == null) {
+            Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
+            ServiceConnection connection = new ServiceBinderConnection();
+
+            if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+                handleFailedConnection();
+                return false;
+            }
+        } else {
+            Preconditions.checkNotNull(mBinder);
+            handleSuccessfulConnection(mBinder);
+        }
+
+        return true;
+    }
+
+    /**
+     * Unbinds from the service if already bound, no-op otherwise.
+     */
+    final void unbind() {
+        ThreadUtil.checkOnMainThread();
+
+        if (mServiceConnection == null) {
+            // We're not yet bound, so queue up an abort request.
+            mIsBindingAborted = true;
+        } else {
+            mContext.unbindService(mServiceConnection);
+            mServiceConnection = null;
+            mBinder = null;
+        }
+    }
+
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Handles a successful attempt to bind to service. See {@link Context#bindService}.
+     *
+     * @param service The actual bound service implementation.
+     */
+    protected abstract void handleSuccessfulConnection(IBinder binder);
+
+    /**
+     * Handles a failed attempt to bind to service. See {@link Context#bindService}.
+     */
+    protected abstract void handleFailedConnection();
+
+    /**
+     * Handles a service disconnection.
+     */
+    protected abstract void handleServiceDisconnected();
+
+    private void clearAbort() {
+        mIsBindingAborted = false;
+    }
+}
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index c78cefe..19c6006 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -18,7 +18,6 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import android.content.Context;
@@ -29,7 +28,6 @@
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -113,7 +111,7 @@
         Call call = new Call(handle, contactInfo);
         boolean bailout = false;
         if (isNullOrEmpty(mCallServices)) {
-            mCallServiceFinder.initiateLookup(context);
+            mCallServiceFinder.initiateLookup();
             bailout = true;
         }
         if (isNullOrEmpty(mSelectors)) {