Follow up on c/401744.
Change-Id: I53bdfae84532c9f382d3ebfa8ab7c993898725b7
diff --git a/src/com/android/telecomm/CallServiceFinder.java b/src/com/android/telecomm/CallServiceFinder.java
index aa9f058..360c503 100644
--- a/src/com/android/telecomm/CallServiceFinder.java
+++ b/src/com/android/telecomm/CallServiceFinder.java
@@ -19,17 +19,23 @@
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;
+import android.os.IBinder;
import android.telecomm.ICallService;
import android.telecomm.ICallServiceProvider;
import android.util.Log;
-import com.android.telecomm.CallServiceProviderProxy.CallServiceProviderConnectionCallback;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
/**
* Finds {@link ICallService} and {@link ICallServiceProvider} implementations on the device.
@@ -38,94 +44,282 @@
* TODO(santoscordon): Add performance timing to async calls.
*/
final class CallServiceFinder {
+
/**
- * Implemented by classes which want to receive the final list of {@link CallService}s found.
+ * Helper class to register/unregister call-service providers.
*/
- interface CallServiceSearchCallback {
+ private class ProviderRegistrar {
+
/**
- * Method called after search has completed.
- *
- * @param callServices List of {@link ICallServices} found in the search.
+ * The name of the call-service provider that is expected to register with this finder.
*/
- public void onSearchComplete(List<ICallService> callServices);
+ private ComponentName mProviderName;
+
+ /**
+ * A unique identifier for a given lookup cycle, see nextLookupId.
+ * TODO(gilad): Potentially unnecessary, consider removing.
+ */
+ int mLookupId;
+
+ /**
+ * Persists the specified parameters.
+ *
+ * @param providerName The component name of the relevant provider.
+ * @param lookupId The lookup-cycle ID.
+ */
+ ProviderRegistrar(ComponentName providerName, int lookupId) {
+ this.mProviderName = providerName;
+ this.mLookupId = lookupId;
+ }
+
+ ComponentName getProviderName() {
+ return mProviderName;
+ }
+
+ /**
+ * Registers the specified call-service provider.
+ *
+ * @param provider The provider object to register.
+ */
+ void register(ICallServiceProvider provider) {
+ registerProvider(mLookupId, mProviderName, provider);
+ }
+
+ /** Unregisters this provider. */
+ void unregister() {
+ unregisterProvider(mProviderName);
+ }
+ }
+
+ /**
+ * Wrapper around ICallServiceProvider, mostly used for binding etc.
+ *
+ * TODO(gilad): Consider making this wrapper unnecessary.
+ */
+ private class ProviderWrapper {
+
+ /**
+ * Persists the specified parameters and attempts to bind the specified provider.
+ *
+ * TODO(gilad): Consider embedding ProviderRegistrar into this class and do away
+ * with the former, or vice versa.
+ *
+ * @param context The relevant application context.
+ * @param registrar The registrar with which to register and unregister this provider.
+ */
+ ProviderWrapper(Context context, final ProviderRegistrar registrar) {
+ ComponentName name = registrar.getProviderName();
+ Preconditions.checkNotNull(name);
+ Preconditions.checkNotNull(context);
+
+ Intent serviceIntent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(name);
+ 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) {
+ registrar.register(ICallServiceProvider.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ registrar.unregister();
+ }
+ };
+
+ if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+ // TODO(santoscordon): Handle error.
+ }
+ }
+ }
+
+ /**
+ * A timer task to ensure each lookup cycle is time-bound, see LOOKUP_TIMEOUT.
+ */
+ private class LookupTerminator extends TimerTask {
+ @Override
+ public void run() {
+ terminateLookup();
+ }
}
/** Used to identify log entries by this class */
- static final String TAG = CallServiceFinder.class.getSimpleName();
-
- /** Private constructor to prevent instances being made. */
- private CallServiceFinder() {}
+ private static final String TAG = CallServiceFinder.class.getSimpleName();
/**
- * Asynchronously finds {@link ICallService} implementations and returns them asynchronously
- * through the callback parameter.
- *
- * @param searchCallback The callback executed when the search is complete.
+ * The longest period in milliseconds each lookup cycle is allowed to span over, see mTimer.
+ * TODO(gilad): Likely requires tuning.
*/
- public static void findCallServices(Context context,
- final CallServiceSearchCallback searchCallback) {
- List<ComponentName> components = getAllProviderComponents(context);
+ private static final int LOOKUP_TIMEOUT = 100;
- Log.i(TAG, "Found " + components.size() + " implementations for ICallServiceProvider");
+ /**
+ * 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();
- for (ComponentName componentName : components) {
- CallServiceProviderProxy proxy = new CallServiceProviderProxy(componentName, context);
- CallServiceProviderConnectionCallback onProviderFoundCallback =
- new CallServiceProviderConnectionCallback() {
- @Override public void onConnected(ICallServiceProvider serviceProvider) {
- onProviderFound(serviceProvider, searchCallback);
- }
- };
+ /**
+ * Determines whether or not a lookup cycle is already running.
+ */
+ private boolean mIsLookupInProgress = false;
- proxy.connect(onProviderFoundCallback);
+ /**
+ * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
+ */
+ private int mNextLookupId = 0;
+
+ /**
+ * The set of bound call-service providers. Only populated via initiateLookup scenarios.
+ * Providers should only be removed upon unbinding.
+ */
+ private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
+
+ /**
+ * 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
+ * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
+ * Note that cached providers do not require finding and hence are excluded from this count.
+ * Also noteworthy is that providers are dynamically removed from this set as they register.
+ */
+ private Set<ComponentName> mUnregisteredProviders;
+
+ /**
+ * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
+ * period, see LOOKUP_TIMEOUT.
+ */
+ private Timer mTimer;
+
+ /**
+ * Initiates a lookup cycle for call-service providers.
+ * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
+ *
+ * @param context The relevant application context.
+ */
+ public synchronized void initiateLookup(Context context) {
+ if (mIsLookupInProgress) {
+ // At most one active lookup is allowed at any given time, bail out.
+ return;
+ }
+
+ List<ComponentName> providerNames = getProviderNames(context);
+ if (providerNames.isEmpty()) {
+ Log.i(TAG, "No ICallServiceProvider implementations found.");
+ updateSwitchboard();
+ return;
+ }
+
+ 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.
+ ProviderRegistrar registrar = new ProviderRegistrar(name, lookupId);
+ new ProviderWrapper(context, registrar);
+ mUnregisteredProviders.add(name);
+ }
+ }
+
+ int providerCount = providerNames.size();
+ int unregisteredProviderCount = mUnregisteredProviders.size();
+
+ Log.i(TAG, "Found " + providerCount + " implementations for ICallServiceProvider, "
+ + unregisteredProviderCount + " of which are not currently registered.");
+
+ if (unregisteredProviderCount == 0) {
+ // All known (provider) implementations are already registered, pass control
+ // back to the switchboard.
+ updateSwitchboard();
+ } else {
+ // Start the timeout for this lookup cycle.
+ // TODO(gilad): Consider reusing the same timer instead of creating new ones.
+ if (mTimer != null) {
+ // Shouldn't be running but better safe than sorry.
+ mTimer.cancel();
+ }
+ mTimer = new Timer();
+ mTimer.schedule(new LookupTerminator(), LOOKUP_TIMEOUT);
}
}
/**
- * Called after a {@link CallServiceProviderProxy} attempts to bind to its
- * {@link ICallServiceProvider} counterpart. When this method is called, the proxy should
- * have either made a successful connection or an error occurred.
+ * Returns the all-inclusive list of call-service-provider names.
*
- * @param serviceProvider The instance of ICallServiceProvider.
+ * @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 static void onProviderFound(ICallServiceProvider serviceProvider,
- CallServiceSearchCallback searchCallback) {
- if (serviceProvider == null) {
- // TODO(santoscordon): Handle error.
- }
-
- Log.i(TAG, "Found a service Provider: " + serviceProvider);
-
- // TODO(santoscordon): asynchronously retrieve ICallService interfaces.
- // TODO(santoscordon): Filter the list by only those which the user has allowed.
- }
-
- private static List<ComponentName> getAllProviderComponents(Context context) {
- Intent serviceIntent = getICallServiceProviderIntent();
+ private List<ComponentName> getProviderNames(Context context) {
+ // The list of provider names to return to the caller, may be populated below.
+ List<ComponentName> providerNames = Lists.newArrayList();
PackageManager packageManager = context.getPackageManager();
- List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(serviceIntent, 0);
-
- List<ComponentName> components = Lists.newArrayList();
- for (ResolveInfo resolveInfo : resolveInfos) {
- ServiceInfo serviceInfo = resolveInfo.serviceInfo;
- // Ignore anything that didn't resolve to a proper service.
- if (serviceInfo == null) {
- continue;
+ Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
+ for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
+ ServiceInfo serviceInfo = entry.serviceInfo;
+ if (serviceInfo != null) {
+ // The entry resolves to a proper service, add it to the list of provider names.
+ providerNames.add(
+ new ComponentName(serviceInfo.packageName, serviceInfo.name));
}
-
- ComponentName componentName = new ComponentName(serviceInfo.packageName,
- serviceInfo.name);
- components.add(componentName);
}
- return components;
+ return providerNames;
}
/**
- * Returns the intent used to resolve all registered {@link ICallService}s.
+ * Registers the specified provider and performs the necessary 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 object to register.
*/
- private static Intent getICallServiceProviderIntent() {
- return new Intent(ICallServiceProvider.class.getName());
+ private void registerProvider(
+ int lookupId, ComponentName providerName, ICallServiceProvider provider) {
+
+ if (mUnregisteredProviders.remove(providerName)) {
+ mProviderRegistry.add(provider);
+ if (mUnregisteredProviders.size() < 1) {
+ terminateLookup(); // No other providers to wait for.
+ }
+ }
+ }
+
+ /**
+ * Unregisters the specified provider.
+ *
+ * @param providerName The component name of the relevant provider.
+ */
+ private void unregisterProvider(ComponentName providerName) {
+ mProviderRegistry.remove(providerName);
+ }
+
+ /**
+ * Timeouts the current lookup cycle, see LookupTerminator.
+ */
+ private void terminateLookup() {
+ if (mTimer != null) {
+ mTimer.cancel(); // Terminate the timer thread.
+ }
+
+ updateSwitchboard();
+ mIsLookupInProgress = false;
+ }
+
+ /**
+ * Updates the switchboard passing the relevant call services (as opposed
+ * to call-service providers).
+ */
+ private void updateSwitchboard() {
+ synchronized (mProviderRegistry) {
+ // TODO(gilad): More here.
+ }
}
}
diff --git a/src/com/android/telecomm/CallServiceProviderProxy.java b/src/com/android/telecomm/CallServiceProviderProxy.java
deleted file mode 100644
index b515850..0000000
--- a/src/com/android/telecomm/CallServiceProviderProxy.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2013 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.telecomm.ICallServiceProvider;
-import android.util.Log;
-
-import com.google.common.base.Preconditions;
-
-/**
- * A proxy to a bound CallServiceProvider implementation. Given a {@link ComponentName}, this class
- * will bind, maintain and unbind a connection with a CallServiceProvider.
- */
-class CallServiceProviderProxy {
- /**
- * Interface used for notifying when the {@link ICallServiceProvider} is bound.
- */
- public interface CallServiceProviderConnectionCallback {
- public void onConnected(ICallServiceProvider provider);
- }
-
- /** Used to identify log entries by this class */
- static final String TAG = CallServiceFinder.class.getSimpleName();
-
- /** Context used to bind with ICallServiceProvider. */
- private final Context mContext;
-
- /**
- * Explicit component name of of the ICallServiceProvider implementation with which to bind.
- */
- private final ComponentName mComponentName;
-
- /**
- * Persists the specified parameters.
- */
- public CallServiceProviderProxy(ComponentName componentName, Context context) {
- mComponentName = Preconditions.checkNotNull(componentName);
- mContext = Preconditions.checkNotNull(context);
- }
-
- /**
- * Binds with the {@link ICallServiceProvider} implementation specified by
- * {@link #mComponentName}.
- */
- public void connect(final CallServiceProviderConnectionCallback connectionCallback) {
- // TODO(santoscordon): Are there cases where we are already connected and should return
- // early with a saved instance?
-
- Intent serviceIntent = getServiceIntent();
- Log.i(TAG, "Binding to ICallService through " + serviceIntent);
-
- // Connection object for the service binding.
- ServiceConnection connection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- onConnected(ICallServiceProvider.Stub.asInterface(service), this,
- connectionCallback);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- onDisconnected(this);
- }
- };
-
- if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
- // TODO(santoscordon): Handle error
- }
-
- // At this point, we should get called on onServiceConnected asynchronously
- }
-
- /**
- * Returns the service {@link Intent} used to bind to {@link ICallServiceProvider} instances.
- */
- private Intent getServiceIntent() {
- Intent serviceIntent = new Intent(ICallServiceProvider.class.getName());
- serviceIntent.setComponent(mComponentName);
- return serviceIntent;
- }
-
- /**
- * Called when an instance of ICallServiceProvider is bound to this process.
- *
- * @param serviceProvider The {@link ICallServiceProvider} instance that was bound.
- * @param connection The service connection used to bind to serviceProvider.
- */
- private void onConnected(ICallServiceProvider serviceProvider, ServiceConnection connection,
- CallServiceProviderConnectionCallback connectionCallback) {
- // TODO(santoscordon): add some error conditions
-
- connectionCallback.onConnected(serviceProvider);
- }
-
- /**
- * Called when ICallServiceProvider is disconnected. This could be for any reason including
- * the host process dying.
- *
- * @param connection The service connection used to bind initially.
- */
- private void onDisconnected(ServiceConnection connection) {
- // TODO(santoscordon): How to handle disconnection?
- }
-}
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index 71c666d..ce8674f 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -22,7 +22,6 @@
import com.android.telecomm.exceptions.RestrictedCallException;
import com.google.common.collect.Lists;
-import java.util.ArrayList;
import java.util.List;
/**
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index 6ee4d22..b33df50 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -23,7 +23,6 @@
import android.telecomm.ICallService;
import android.util.Log;
-import com.android.telecomm.CallServiceFinder.CallServiceSearchCallback;
import com.android.telecomm.exceptions.CallServiceUnavailableException;
import com.android.telecomm.exceptions.OutgoingCallException;
@@ -35,98 +34,97 @@
* considered a different transport type).
* TODO(santoscordon): Need to add comments on the switchboard optimizer once that it is place.
*/
-class Switchboard {
+final class Switchboard {
/** Used to identify log entries by this class */
private static final String TAG = Switchboard.class.getSimpleName();
+ private CallServiceFinder callServiceFinder = new CallServiceFinder();
+
/**
* Places an outgoing call to the handle passed in. Method asynchronously collects
* {@link ICallService} implementations and passes them along with the handle and contactInfo
* to {@link #placeOutgoingCallInternal} to actually place the call.
*
- * @param handle The handle to dial.
- * @param contactInfo Information about the entity being called.
+ * @param handle The handle to dial. Marked as final so it can be used in the inner class.
+ * @param contactInfo Information about the entity being called. Marked as final so it can
+ * be used in the inner class.
* @param context The application context.
*/
- void placeOutgoingCall(final String handle, final ContactInfo contactInfo,
- final Context context) {
+ void placeOutgoingCall(String handle, ContactInfo contactInfo, Context context) {
+ callServiceFinder.initiateLookup(context);
- CallServiceFinder.findCallServices(context, new CallServiceSearchCallback() {
- @Override
- public void onSearchComplete(List<ICallService> callServices) {
- try {
- placeOutgoingCallInternal(handle, contactInfo, callServices);
- } catch (CallServiceUnavailableException e) {
- // TODO(santoscordon): Handle error
- }
- }
- });
+ // TODO(gilad): Persist the necessary parameters to attempt putting the call through
+ // once the call services become available (likely using some sort of closure).
}
/**
* Places an outgoing call to the handle passed in. Given a list of {@link ICallServices},
* select one and place a call to the handle.
* TODO(santoscordon): How does the CallService selection process work?
+ * TODO(gilad): Wire this logic from CallServiceFinder.updateSwitchboard.
*
* @param handle The handle to dial.
* @param contactInfo Information about the entity being called.
* @param callServices The list of available {@link ICallService}s.
*/
- private void placeOutgoingCallInternal(String handle, ContactInfo contactInfo,
- List<ICallService> callServices) throws CallServiceUnavailableException {
- Log.i(TAG, "Placing and outgoing call.");
-
- if (callServices.isEmpty()) {
- // No call services, bail out.
- // TODO(contacts-team): Add logging?
- // TODO(santoscordon): Does this actually go anywhere considering this method is now
- // asynchronous?
- throw new CallServiceUnavailableException("No CallService found.");
- }
-
- List<ICallService> compatibleCallServices = Lists.newArrayList();
- for (ICallService service : callServices) {
- // TODO(santoscordon): This code needs to be updated to an asynchronous response
- // callback from isCompatibleWith().
- /* if (service.isCompatibleWith(handle)) {
- // NOTE(android-contacts): If we end up taking the liberty to issue
- // calls not using the explicit user input (in case one is provided)
- // and instead pull an alternative method of communication from the
- // specified user-info object, it may be desirable to give precedence
- // to services that can in fact respect the user's intent.
- compatibleCallServices.add(service);
- }
- */
- }
-
- if (compatibleCallServices.isEmpty()) {
- // None of the available call services is suitable for making this call.
- // TODO(contacts-team): Same here re logging.
- throw new CallServiceUnavailableException("No compatible CallService found.");
- }
-
- // NOTE(android-team): At this point we can also prompt the user for
- // preference, i.e. instead of the logic just below.
- if (compatibleCallServices.size() > 1) {
- compatibleCallServices = sort(compatibleCallServices);
- }
- for (ICallService service : compatibleCallServices) {
- try {
- service.call(handle);
- return;
- } catch (RemoteException e) {
- // TODO(santoscordon): Need some proxy for ICallService so that we don't have to
- // avoid RemoteExceptionHandling everywhere. Take a look at how InputMethodService
- // handles this.
- }
- // catch (OutgoingCallException ignored) {
- // TODO(santoscordon): Figure out how OutgoingCallException falls into this. Should
- // RemoteExceptions also be converted to OutgoingCallExceptions thrown by call()?
- }
- }
+// private void placeOutgoingCallInternal(
+// String handle,
+// ContactInfo contactInfo,
+// List<ICallService> callServices) throws CallServiceUnavailableException {
+//
+// Log.i(TAG, "Placing and outgoing call.");
+//
+// if (callServices.isEmpty()) {
+// // No call services, bail out.
+// // TODO(contacts-team): Add logging?
+// // TODO(santoscordon): Does this actually go anywhere considering this method is now
+// // asynchronous?
+// throw new CallServiceUnavailableException("No CallService found.");
+// }
+//
+// List<ICallService> compatibleCallServices = Lists.newArrayList();
+// for (ICallService service : callServices) {
+// // TODO(santoscordon): This code needs to be updated to an asynchronous response
+// // callback from isCompatibleWith().
+// /* if (service.isCompatibleWith(handle)) {
+// // NOTE(android-contacts): If we end up taking the liberty to issue
+// // calls not using the explicit user input (in case one is provided)
+// // and instead pull an alternative method of communication from the
+// // specified user-info object, it may be desirable to give precedence
+// // to services that can in fact respect the user's intent.
+// compatibleCallServices.add(service);
+// }
+// */
+// }
+//
+// if (compatibleCallServices.isEmpty()) {
+// // None of the available call services is suitable for making this call.
+// // TODO(contacts-team): Same here re logging.
+// throw new CallServiceUnavailableException("No compatible CallService found.");
+// }
+//
+// // NOTE(android-team): At this point we can also prompt the user for
+// // preference, i.e. instead of the logic just below.
+// if (compatibleCallServices.size() > 1) {
+// compatibleCallServices = sort(compatibleCallServices);
+// }
+// for (ICallService service : compatibleCallServices) {
+// try {
+// service.call(handle);
+// return;
+// } catch (RemoteException e) {
+// // TODO(santoscordon): Need some proxy for ICallService so that we don't have to
+// // avoid RemoteExceptionHandling everywhere. Take a look at how InputMethodService
+// // handles this.
+// }
+// // catch (OutgoingCallException ignored) {
+// // TODO(santoscordon): Figure out how OutgoingCallException falls into this. Should
+// // RemoteExceptions also be converted to OutgoingCallExceptions thrown by call()?
+// }
+// }
/**
- * Sorts a list of {@link ICallService} ordered by the prefered service for dialing the call.
+ * Sorts a list of {@link ICallService} ordered by the preferred service for dialing the call.
*
* @param callServices The list to order.
*/