Adding the lookup logic for call-service selectors.

Change-Id: If18628f9b4854b8055ec43d8e33c0c6f822454cd
diff --git a/src/com/android/telecomm/CallServiceFinder.java b/src/com/android/telecomm/CallServiceFinder.java
index 898877f..4ee73c3 100644
--- a/src/com/android/telecomm/CallServiceFinder.java
+++ b/src/com/android/telecomm/CallServiceFinder.java
@@ -71,6 +71,8 @@
 
     /**
      * Used to generate unique lookup-cycle identifiers. 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;
 
@@ -87,8 +89,8 @@
      * 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.
+     * Cached providers do not require finding and hence are excluded from this set.  Entries are
+     * removed from this set as providers register.
      */
     private Set<ComponentName> mUnregisteredProviders;
 
@@ -151,7 +153,7 @@
         int providerCount = providerNames.size();
         int unregisteredProviderCount = mUnregisteredProviders.size();
 
-        Log.i(TAG, "Found " + providerCount + " implementations for ICallServiceProvider, "
+        Log.i(TAG, "Found " + providerCount + " implementations of ICallServiceProvider, "
                 + unregisteredProviderCount + " of which are not currently registered.");
 
         if (unregisteredProviderCount == 0) {
@@ -198,7 +200,7 @@
      * @param lookupId The lookup-cycle ID.
      * @param context The relevant application context.
      */
-    void bindProvider(
+    private void bindProvider(
             final ComponentName providerName, final int lookupId, Context context) {
 
         Preconditions.checkNotNull(providerName);
@@ -263,6 +265,8 @@
      * current lookup cycle.
      * TODO(santoscordon): Consider replacing this method's use of synchronized with a Handler
      * queue.
+     * TODO(gilad): Santos has some POC code to make synchronized (below) unnecessary, either
+     * move to use that or remove this to-do.
      *
      * @param lookupId The lookup-cycle ID.
      * @param providerName The component name of the relevant provider.
@@ -276,7 +280,7 @@
             List<ICallService> callServices) {
 
         // TODO(santoscordon): When saving the call services into this class, also add code to
-        // unregister (remove) the call services upon disconnect. Potenially use RemoteCallbackList.
+        // unregister (remove) the call services upon disconnect. Potentially use RemoteCallbackList.
 
         if (mUnregisteredProviders.remove(providerName)) {
             mProviderRegistry.add(provider);
@@ -314,6 +318,7 @@
      */
     private void updateSwitchboard() {
         ThreadUtil.checkOnMainThread();
+
         // TODO(gilad): More here.
         mSwitchboard.setCallServices(null);
     }
diff --git a/src/com/android/telecomm/CallServiceSelectorFinder.java b/src/com/android/telecomm/CallServiceSelectorFinder.java
new file mode 100644
index 0000000..50a4393
--- /dev/null
+++ b/src/com/android/telecomm/CallServiceSelectorFinder.java
@@ -0,0 +1,275 @@
+/*
+ * 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.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.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.telecomm.ICallServiceSelector;
+import android.util.Log;
+
+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;
+
+/**
+ * Helper class to retrieve {@link ICallServiceSelector} implementations on the device and
+ * asynchronously bind to them.  Each lookup cycle is time-boxed, hence selectors too slow
+ * to bind are effectively omitted from the set that is passed back to {@link Switchboard}.
+ */
+final class CallServiceSelectorFinder {
+
+    private static final String TAG = CallServiceSelectorFinder.class.getSimpleName();
+
+    /**
+     * The longest period in milliseconds each lookup cycle is allowed to span over, see
+     * {@link #mLookupTerminator}.
+     * TODO(gilad): Likely requires tuning.
+     */
+    private static final int LOOKUP_TIMEOUT_MS = 100;
+
+    /**
+     * Used to retrieve all known ICallServiceSelector implementations from the framework.
+     */
+    private static final String CALL_SERVICE_SELECTOR_CLASS_NAME =
+            ICallServiceSelector.class.getName();
+
+    /**
+     * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
+     * period, see LOOKUP_TIMEOUT.
+     */
+    private final Runnable mLookupTerminator = new Runnable() {
+        @Override
+        public void run() {
+            terminateLookup();
+        }
+    };
+
+    /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private final Switchboard mSwitchboard;
+
+    /**
+     * Determines whether or not a lookup cycle is already running.
+     */
+    private boolean mIsLookupInProgress = false;
+
+    /**
+     * Used to generate unique lookup-cycle identifiers. 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;
+
+    /**
+     * The set of bound call-service selectors.  Only populated via initiateLookup scenarios.
+     * Selectors should only be removed upon unbinding.
+     */
+    private Set<ICallServiceSelector> mSelectorRegistry = Sets.newHashSet();
+
+    /**
+     * Stores the names of the selectors to bind to in one lookup cycle.  The set size represents
+     * the number of call-service selectors this finder expects to hear back from upon initiating
+     * lookups, see initiateLookup. Whenever all selectors respond before the timeout occurs, the
+     * complete set of available selectors 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 selectors
+     * do not require finding and hence are excluded from this set.  Also note that selectors are
+     * removed from this set as they register.
+     */
+    private Set<ComponentName> mUnregisteredSelectors;
+
+    /**
+     * Persists the specified parameters.
+     *
+     * @param switchboard The switchboard for this finer to work against.
+     */
+    CallServiceSelectorFinder(Switchboard switchboard) {
+        mSwitchboard = switchboard;
+    }
+
+    /**
+     * Initiates a lookup cycle for call-service selectors. Must be called from the UI thread.
+     *
+     * @param context The relevant application context.
+     */
+    void initiateLookup(Context context) {
+        ThreadUtil.checkOnMainThread();
+        if (mIsLookupInProgress) {
+            // At most one active lookup is allowed at any given time, bail out.
+            return;
+        }
+
+        List<ComponentName> selectorNames = getSelectorNames(context);
+        if (selectorNames.isEmpty()) {
+            Log.i(TAG, "No ICallServiceSelector implementations found.");
+            updateSwitchboard();
+            return;
+        }
+
+        mIsLookupInProgress = true;
+        mUnregisteredSelectors = Sets.newHashSet();
+
+        int lookupId = mNextLookupId++;
+        for (ComponentName name : selectorNames) {
+            if (!mSelectorRegistry.contains(name)) {
+                // The selector is either not yet registered or has been unregistered
+                // due to unbinding etc.
+                mUnregisteredSelectors.add(name);
+                bindSelector(name, lookupId, context);
+            }
+        }
+
+        int selectorCount = selectorNames.size();
+        int unregisteredSelectorCount = mUnregisteredSelectors.size();
+
+        Log.i(TAG, "Found " + selectorCount + " implementations of ICallServiceSelector, "
+                + unregisteredSelectorCount + " of which are not currently registered.");
+
+        if (unregisteredSelectorCount == 0) {
+            // All known (selector) implementations are already registered, pass control
+            // back to the switchboard.
+            updateSwitchboard();
+        } else {
+            // Schedule a lookup terminator to run after LOOKUP_TIMEOUT_MS milliseconds.
+            mHandler.removeCallbacks(mLookupTerminator);
+            mHandler.postDelayed(mLookupTerminator, LOOKUP_TIMEOUT_MS);
+        }
+    }
+
+    /**
+     * Returns the all-inclusive list of call-service selector names.
+     *
+     * @param context The relevant/application context to query against.
+     * @return The list containing the (component) names of all known ICallServiceSelector
+     *     implementations or the empty list upon no available selectors.
+     */
+    private List<ComponentName> getSelectorNames(Context context) {
+        // The list of selector names to return to the caller, may be populated below.
+        List<ComponentName> selectorNames = Lists.newArrayList();
+
+        PackageManager packageManager = context.getPackageManager();
+        Intent intent = new Intent(CALL_SERVICE_SELECTOR_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 selector names.
+                selectorNames.add(
+                        new ComponentName(serviceInfo.packageName, serviceInfo.name));
+            }
+        }
+
+        return selectorNames;
+    }
+
+    /**
+     * Attempts to bind the specified selector and have it register upon successful binding.
+     * Also performs the necessary wiring to unregister the selector upon un-binding.
+     *
+     * @param selectorName The component name of the relevant selector.
+     * @param lookupId The lookup-cycle ID.
+     * @param context The relevant application context.
+     */
+    private void bindSelector(
+            final ComponentName selectorName, final int lookupId, Context context) {
+
+        Preconditions.checkNotNull(selectorName);
+        Preconditions.checkNotNull(context);
+
+        Intent serviceIntent =
+                new Intent(CALL_SERVICE_SELECTOR_CLASS_NAME).setComponent(selectorName);
+        Log.i(TAG, "Binding to ICallServiceSelector through " + serviceIntent);
+
+        // Connection object for the service binding.
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                ICallServiceSelector selector = ICallServiceSelector.Stub.asInterface(service);
+                registerSelector(lookupId, selectorName, selector);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                unregisterSelector(selectorName);
+            }
+        };
+
+        if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+            // TODO(gilad): Handle error.
+        }
+    }
+
+    /**
+     * Registers the specified selector.
+     *
+     * @param lookupId The lookup-cycle ID.  Currently unused, consider removing.
+     * @param selectorName The component name of the relevant selector.
+     * @param selector The selector object to register.
+     */
+    private void registerSelector(
+            int lookupId, ComponentName selectorName, ICallServiceSelector selector) {
+
+        ThreadUtil.checkOnMainThread();
+
+        if (mUnregisteredSelectors.remove(selectorName)) {
+            mSelectorRegistry.add(selector);
+            if (mUnregisteredSelectors.size() < 1) {
+                terminateLookup();  // No other selectors to wait for.
+            }
+        }
+    }
+
+    /**
+     * Unregisters the specified selector.
+     *
+     * @param selectorName The component name of the relevant selector.
+     */
+    private void unregisterSelector(ComponentName selectorName) {
+        mSelectorRegistry.remove(selectorName);
+    }
+
+    /**
+     * Timeouts the current lookup cycle, see LookupTerminator.
+     */
+    private void terminateLookup() {
+        mHandler.removeCallbacks(mLookupTerminator);
+
+        updateSwitchboard();
+        mIsLookupInProgress = false;
+    }
+
+    /**
+     * Updates the switchboard passing the relevant call services selectors.
+     */
+    private void updateSwitchboard() {
+        ThreadUtil.checkOnMainThread();
+
+        // TODO(gilad): More here.
+        mSwitchboard.setSelectors(null);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index e9f9033..4abebce 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -22,11 +22,13 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.telecomm.ICallService;
+import android.telecomm.ICallServiceSelector;
 import android.util.Log;
 
 import com.android.telecomm.exceptions.CallServiceUnavailableException;
 import com.android.telecomm.exceptions.OutgoingCallException;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -42,7 +44,17 @@
 
     private static final String TAG = Switchboard.class.getSimpleName();
 
-    private CallServiceFinder callServiceFinder = new CallServiceFinder(this);
+    private CallServiceFinder mCallServiceFinder = new CallServiceFinder(this);
+
+    private CallServiceSelectorFinder mSelectorFinder = new CallServiceSelectorFinder(this);
+
+    /** TODO(gilad): Add comment, may also want to use a set instead. */
+    /** TODO(gilad): Null out once the active-call count goes to zero. */
+    private List<ICallService> mCallServices;
+
+    /** TODO(gilad): Add comment, may also want to use a set instead. */
+    /** TODO(gilad): Null out once the active-call count goes to zero. */
+    private List<ICallServiceSelector> mSelectors;
 
     private Set<Call> mPendingOutgoingCalls = Sets.newHashSet();
 
@@ -50,6 +62,7 @@
      * 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.
+     * TODO(gilad): Update.
      *
      * @param handle The handle to dial.
      * @param contactInfo Information about the entity being called.
@@ -57,41 +70,109 @@
      */
     void placeOutgoingCall(String handle, ContactInfo contactInfo, Context context) {
         ThreadUtil.checkOnMainThread();
-        mPendingOutgoingCalls.add(new Call(handle, contactInfo));
-        callServiceFinder.initiateLookup(context);
+
+        // TODO(gilad): Consider creating the call object even earlier, e.g. in CallsManager.
+        Call call = new Call(handle, contactInfo);
+        boolean bailout = false;
+        if (isNullOrEmpty(mCallServices)) {
+            mCallServiceFinder.initiateLookup(context);
+            bailout = true;
+        }
+        if (isNullOrEmpty(mSelectors)) {
+            mSelectorFinder.initiateLookup(context);
+            bailout = true;
+        }
+
+        if (bailout) {
+            // Unable to process the call without either call service, selectors, or both.
+            // Store the call for deferred processing and bail out.
+            mPendingOutgoingCalls.add(call);
+            return;
+        }
+
+        placeOutgoingCall(call);
     }
 
     /**
      * Persists the specified list of call services and attempts to connect any pending outgoing
-     * calls still waiting for a matching call-service to be initiated.
+     * calls still waiting for a matching call-service to be initiated. Intended to be called by
+     * {@link CallServiceFinder} exclusively.
      *
-     * @param callServices The potentially-partial list of call services the switchboard should
-     *     feel free to make use of.  Partial since the lookup procedure is time-boxed such that
-     *     some providers/call-services may be too slow to respond and hence effectively omitted
-     *     from the specified list.  If the switchboard has previous/reliable knowledge of other
-     *     call-services, it should be at liberty to use these just as well.
+     * @param callServices The potentially-partial list of call services.  Partial since the
+     *     lookup procedure is time-boxed, such that some providers/call-services may be slow
+     *     to respond and hence effectively omitted from the specified list.
      */
     void setCallServices(List<ICallService> callServices) {
         ThreadUtil.checkOnMainThread();
-        for (Call pendingCall : mPendingOutgoingCalls) {
-            // TODO(gilad): Iterate through the prioritized list of switchboard policies passing
-            // to each policy the call object as well as all known call services.  Break out of
-            // the inner/policy loop as soon as the first matching policy for the call is found.
-            // Calls for which no matching policy can be found will be killed by cleanup/monitor
-            // thread, see the "monitor" to-do at the top of the file.
 
-            // Psuedo code (assuming connect to be a future switchboard method):
-            //
-            //   FOR policy IN prioritizedPolicies:
-            //     IF policy.is_applicable_to(pendingCall, callServices):
-            //       TRY
-            //         connect(pendingCall, callServices, policy)
-            //         mPendingOutgoingCalls.remove(pendingCall)
-            //         BREAK
+        mCallServices = callServices;
+        processPendingOutgoingCalls();
+    }
+
+    /**
+     * Persists the specified list of selectors and attempts to connect any pending outgoing
+     * calls.  Intended to be called by {@link CallServiceSelectorFinder} exclusively.
+     *
+     * @param selectors The potentially-partial list of selectors.  Partial since the lookup
+     *     procedure is time-boxed, such that some selectors may be slow to respond and hence
+     *     effectively omitted from the specified list.
+     */
+    void setSelectors(List<ICallServiceSelector> selectors) {
+        ThreadUtil.checkOnMainThread();
+
+        mSelectors = selectors;
+        processPendingOutgoingCalls();
+    }
+
+    /**
+     * Attempts to process any pending outgoing calls that have not yet been expired.
+     */
+    void processPendingOutgoingCalls() {
+        for (Call call : mPendingOutgoingCalls) {
+            placeOutgoingCall(call);
         }
     }
 
     /**
+     * Attempts to place the specified call.
+     *
+     * @param call The call to put through.
+     */
+    private void placeOutgoingCall(Call call) {
+        if (isNullOrEmpty(mCallServices) || isNullOrEmpty(mSelectors)) {
+            // At least one call service and one selector are required to process outgoing calls.
+            return;
+        }
+
+        // TODO(gilad): Iterate through the prioritized list of selectors passing to each selector
+        // (read-only versions of) the call object and all available call services.  Break out once
+        // a matching selector is found. Calls with no matching selectors will eventually be killed
+        // by the cleanup/monitor thread, see the "monitor" to-do at the top of the file.
+
+        // Psuedo code (assuming connect to be a future switchboard method):
+        //
+        //   FOR selector IN prioritizedSelectors:
+        //     prioritizedCallServices = selector.select(mCallServices, call)
+        //     IF notEmpty(prioritizedCallServices):
+        //       FOR callService IN prioritizedCallServices:
+        //         TRY
+        //           connect(call, callService, selector)
+        //           mPendingOutgoingCalls.remove(call)
+        //           BREAK
+    }
+
+    /**
+     * Determines whether or not the specified collection is either null or empty.
+     *
+     * @param collection Either null or the collection object to be evaluated.
+     * @return True if the collection is null or empty.
+     */
+    @SuppressWarnings("rawtypes")
+    private boolean isNullOrEmpty(Collection collection) {
+        return collection == null || collection.isEmpty();
+    }
+
+    /**
      * 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?
@@ -156,16 +237,4 @@
 //            // RemoteExceptions also be converted to OutgoingCallExceptions thrown by call()?
 //        }
 //    }
-
-    /**
-     * Sorts a list of {@link ICallService} ordered by the preferred service for dialing the call.
-     *
-     * @param callServices The list to order.
-     */
-    private List<ICallService> sort(List<ICallService> callServices) {
-        // TODO(android-contacts): Sort by reliability, cost, and ultimately
-        // the desirability to issue a given call over each of the specified
-        // call services.
-        return callServices;
-    }
 }