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)) {