Adding the OutgoingCallProcessor and the initial wiring between it and the switchboard.

Change-Id: Ic331cdf5c8c84ae2a93a42f84aa19e5885bc0768
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index a969158..ef72cd0 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -25,11 +25,16 @@
  */
 final class Call {
 
+    /**
+     * Unique identifier for the call as a UUID string.
+     */
+    private final String mId;
+
     /** The handle with which to establish this call. */
-    private String mHandle;
+    private final String mHandle;
 
     /** Additional contact information beyond handle above, optional. */
-    private ContactInfo mContactInfo;
+    private final ContactInfo mContactInfo;
 
     /**
      * The time this call was created, typically also the time this call was added to the set
@@ -45,18 +50,33 @@
      * @param handle The handle to dial.
      * @param contactInfo Information about the entity being called.
      */
-    public Call(String handle, ContactInfo contactInfo) {
+    Call(String handle, ContactInfo contactInfo) {
+        // TODO(gilad): Pass this in etc.
+        mId = "dummy";
+
         mHandle = handle;
         mContactInfo = contactInfo;
 
         mCreationTime = new Date();
     }
 
+    String getId() {
+        return mId;
+    }
+
+    String getHandle() {
+        return mHandle;
+    }
+
+    ContactInfo getContactInfo() {
+        return mContactInfo;
+    }
+
     /**
      * @return The "age" of this call object in milliseconds, which typically also represents the
      *     period since this call was added to the set pending outgoing calls, see mCreationTime.
      */
-    public long getAgeInMilliseconds() {
+    long getAgeInMilliseconds() {
         return new Date().getTime() - mCreationTime.getTime();
     }
 }
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
new file mode 100644
index 0000000..d6d24bc
--- /dev/null
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -0,0 +1,232 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import android.os.RemoteException;
+import android.telecomm.CallInfo;
+import android.telecomm.ICallService;
+import android.telecomm.ICallServiceSelector;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class to place a call using the specified set of call-services and ordered selectors.
+ * Iterates through the selectors and gets a sorted list of supported call service IDs for each
+ * selector. Upon receiving each sorted list (one list per selector), each of the corresponding
+ * call services is then attempted until either the outgoing call is placed, the attempted call
+ * is aborted (by the switchboard), or the list is exhausted -- whichever occurs first.
+ *
+ * Except for the abort case, all other scenarios should terminate with the switchboard notified.
+ */
+final class OutgoingCallProcessor {
+
+    /**
+     * The (singleton) Telecomm switchboard instance.
+     */
+    private final Switchboard mSwitchboard;
+
+    /**
+     * The outgoing call this processor is tasked with placing.
+     */
+    private final Call mCall;
+
+    /**
+     * The (read-only) object derived from mCall above to pass through outside of the Telecomm
+     * package.
+     */
+    private final CallInfo mCallInfo;
+
+    /**
+     * The set of currently-available call-service IDs.
+     */
+    private final Set<String> mCallServiceIds = Sets.newHashSet();
+
+    /**
+     * The map of currently-available call-service implementations keyed by call-service ID.
+     */
+    private final Map<String, ICallService> mCallServicesById = Maps.newHashMap();
+
+    /**
+     * The set of currently-available call-service selector implementations.
+     */
+    private final List<ICallServiceSelector> mSelectors;
+
+    /**
+     * The iterator over the currently-selected ordered list of call-service IDs.
+     */
+    private Iterator<String> mCallServiceIdIterator;
+
+    private Iterator<ICallServiceSelector> mSelectorIterator;
+
+    private boolean mIsAborted = false;
+
+    /**
+     * Persists the specified parameters and iterates through the prioritized list of selectors
+     * passing to each selector (read-only versions of) the call object and all available call-
+     * service IDs.  Stops once a matching selector is found.  Calls with no matching selectors
+     * will eventually be killed by the cleanup/monitor switchboard handler, which will in turn
+     * call the abort method of this processor.
+     *
+     * @param call The call to place.
+     * @param callServices The available call-service implementations.
+     * @param selectors The available call-service selector implementations.
+     * @param switchboard The switchboard for this processor to work against.
+     */
+    OutgoingCallProcessor(
+            Call call,
+            Set<ICallService> callServices,
+            List<ICallServiceSelector> selectors,
+            Switchboard switchboard) {
+
+        Preconditions.checkNotNull(callServices);
+        Preconditions.checkNotNull(selectors);
+
+        mCall = call;
+        mCallInfo = new CallInfo(call.getId(), call.getHandle());
+        mSelectors = selectors;
+        mSwitchboard = switchboard;
+
+        // Populate the list and map of call-service IDs.  The list is needed since
+        // it's being passed down to selectors.
+        for (ICallService callService : callServices) {
+            // TOOD(gilad): Implement callService.getId() and use that instead.
+            String id = "xyz";
+            mCallServiceIds.add(id);
+            mCallServicesById.put(id, callService);
+        }
+    }
+
+    /**
+     * Initiates the attempt to place the call.  No-op beyond the first invocation.
+     */
+    void process() {
+        if (mSelectors.isEmpty() || mCallServiceIds.isEmpty()) {
+            // TODO(gilad): Consider adding a failure message/type to differentiate the various
+            // cases, or potentially throw an exception in this case.
+            mSwitchboard.handleFailedOutgoingCall(mCall);
+        } else if (mSelectorIterator == null) {
+            mSelectorIterator = mSelectors.iterator();
+            attemptNextSelector();
+        }
+    }
+
+    /**
+     * Aborts the attempt to place the relevant call.  Intended to be invoked by
+     * switchboard.
+     */
+    void abort() {
+        mIsAborted = true;
+    }
+
+    /**
+     * Attempts to place the call using the next selector, no-op if no other selectors
+     * are available.
+     */
+    private void attemptNextSelector() {
+        if (mIsAborted) {
+            return;
+        }
+
+        if (mSelectorIterator.hasNext()) {
+            ICallServiceSelector selector = mSelectorIterator.next();
+
+            // TODO(gilad): Refactor to pass (CallInfo, List<String>, response) passing
+            // mCallInfo as the 1st and mCallServiceIds (or an immutable version thereof)
+            // as the 2nd parameter.
+            // selector.select(handle, callServiceBinders, response);
+
+            // TODO(gilad): Get the list of call-service IDs (asynchronically), store it
+            // (in setSelectedCallServiceIds), and then invoke attemptNextCallService.
+
+            // NOTE(gilad): Currently operating under the assumption that we'll have one timeout
+            // per (outgoing) call attempt.  If we (also) like to timeout individual selectors
+            // and/or call services, the code here will need to be refactored (quite a bit) to
+            // support that.
+
+        } else {
+            mSwitchboard.handleFailedOutgoingCall(mCall);
+        }
+    }
+
+    /**
+     * Persists the ordered-list of call service IDs as selected by the current selector and
+     * starts iterating through the corresponding call services in the continuing attempt to
+     * place the call.
+     * TODO(gilad): Get this to be invoked upon selector.select responses.
+     *
+     * @selectedCallServiceIds The (ordered) list of call service IDs.
+     */
+    private void setSelectedCallServiceIds(List<String> selectedCallServiceIds) {
+        if (selectedCallServiceIds == null || selectedCallServiceIds.isEmpty()) {
+            attemptNextSelector();
+        } else if (mCallServiceIdIterator == null) {
+            mCallServiceIdIterator = selectedCallServiceIds.iterator();
+            attemptNextCallService();
+        }
+    }
+
+    /**
+     * TODO(gilad): Add comment.
+     */
+    private void attemptNextCallService() {
+        if (mIsAborted) {
+            return;
+        }
+
+        if (mCallServiceIdIterator.hasNext()) {
+            String id = mCallServiceIdIterator.next();
+            ICallService callService = mCallServicesById.get(id);
+            if (callService != null) {
+                try {
+                    // TODO(gilad): Refactor to pass a CallInfo object instead.
+                    callService.call(mCallInfo);
+                } catch (RemoteException e) {
+                    // TODO(gilad): Log etc.
+                    attemptNextCallService();
+                }
+            }
+        } else {
+            mCallServiceIdIterator = null;
+            attemptNextSelector();
+        }
+    }
+
+    /**
+     * Handles the successful outgoing-call case.
+     */
+    private void handleSuccessfulOutgoingCall() {
+        // TODO(gilad): More here?
+
+        abort();  // Shouldn't be necessary but better safe than sorry.
+        mSwitchboard.handleSuccessfulOutgoingCall(mCall);
+    }
+
+    /**
+     * Handles the failed outgoing-call case.
+     */
+    private void handleFailedOutgoingCall() {
+        // TODO(gilad): Implement.
+        attemptNextCallService();
+    }
+}
diff --git a/src/com/android/telecomm/Switchboard.java b/src/com/android/telecomm/Switchboard.java
index c4c6028..a1658e6 100644
--- a/src/com/android/telecomm/Switchboard.java
+++ b/src/com/android/telecomm/Switchboard.java
@@ -16,7 +16,9 @@
 
 package com.android.telecomm;
 
+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;
@@ -28,8 +30,10 @@
 import com.android.telecomm.exceptions.CallServiceUnavailableException;
 import com.android.telecomm.exceptions.OutgoingCallException;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -38,7 +42,7 @@
  * considered a different transport type).
  * TODO(santoscordon): Need to add comments on the switchboard optimizer once that it is place.
  * TODO(gilad): Add a monitor thread to wake up periodically and check for stale pending calls
- *     that may need to be terminated, see mPendingOutgoingCalls.
+ *     that may need to be terminated, see mNewOutgoingCalls and mPendingOutgoingCalls.
  */
 final class Switchboard {
 
@@ -61,7 +65,11 @@
      */
     private Set<ICallServiceSelector> mSelectors;
 
-    private Set<Call> mPendingOutgoingCalls = Sets.newHashSet();
+    private Set<Call> mNewOutgoingCalls = Sets.newLinkedHashSet();
+
+    private Set<Call> mPendingOutgoingCalls = Sets.newLinkedHashSet();
+
+    private Map<Call, OutgoingCallProcessor> outgoingCallProcessors = Maps.newHashMap();
 
     /**
      * Places an outgoing call to the handle passed in. Method asynchronously collects
@@ -91,17 +99,16 @@
         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);
+            mNewOutgoingCalls.add(call);
             return;
         }
 
-        placeOutgoingCall(call);
+        processNewOutgoingCall(call);
     }
 
     /**
-     * Persists the specified set of call services and attempts to connect any pending outgoing
-     * calls (still waiting for a matching call-service to be initiated). Intended to be called
-     * by {@link CallServiceFinder} exclusively.
+     * Persists the specified set of call services and attempts to place any pending outgoing
+     * calls.  Intended to be invoked by {@link CallServiceFinder} exclusively.
      *
      * @param callServices The potentially-partial set of call services.  Partial since the lookup
      *     process is time-boxed, such that some providers/call-services may be slow to respond and
@@ -111,12 +118,12 @@
         ThreadUtil.checkOnMainThread();
 
         mCallServices = callServices;
-        processPendingOutgoingCalls();
+        processNewOutgoingCalls();
     }
 
     /**
      * Persists the specified list of selectors and attempts to connect any pending outgoing
-     * calls.  Intended to be called by {@link CallServiceSelectorFinder} exclusively.
+     * calls.  Intended to be invoked by {@link CallServiceSelectorFinder} exclusively.
      *
      * @param selectors The potentially-partial set of selectors.  Partial since the lookup
      *     procedure is time-boxed such that some selectors may be slow to respond and hence
@@ -130,44 +137,74 @@
         // built-in selectors can be implemented in a manner that does not require binding,
         // that's probably preferred.  May want to use a LinkedHashSet for the sorted set.
         mSelectors = selectors;
-        processPendingOutgoingCalls();
+        processNewOutgoingCalls();
     }
 
     /**
-     * Attempts to process any pending outgoing calls that have not yet been expired.
+     * Handles the case where an outgoing call has been successfully placed,
+     * see {@link OutgoingCallProcessor}.
      */
-    void processPendingOutgoingCalls() {
-        for (Call call : mPendingOutgoingCalls) {
-            placeOutgoingCall(call);
+    void handleSuccessfulOutgoingCall(Call call) {
+        // TODO(gilad): More here.
+
+        // Process additional (new) calls, if any.
+        processNewOutgoingCalls();
+    }
+
+    /**
+     * Handles the case where an outgoing call could not be proceed by any of the
+     * selector/call-service implementations, see {@link OutgoingCallProcessor}.
+     */
+    void handleFailedOutgoingCall(Call call) {
+        // TODO(gilad): More here.
+
+        // Process additional (new) calls, if any.
+        processNewOutgoingCalls();
+    }
+
+    /**
+     * Attempts to process the next new outgoing calls that have not yet been expired.
+     */
+    private void processNewOutgoingCalls() {
+        if (isNullOrEmpty(mCallServices) || isNullOrEmpty(mSelectors)) {
+            // At least one call service and one selector are required to process outgoing calls.
+            return;
+        }
+
+        if (!mNewOutgoingCalls.isEmpty()) {
+            Call call = mNewOutgoingCalls.iterator().next();
+            mNewOutgoingCalls.remove(call);
+            mPendingOutgoingCalls.add(call);
+
+            // Specifically only attempt to place one call at a time such that call services
+            // can be freed from needing to deal with concurrent requests.
+            processNewOutgoingCall(call);
         }
     }
 
     /**
      * Attempts to place the specified call.
      *
-     * @param call The call to put through.
+     * @param call The call to place.
      */
-    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;
-        }
+    private void processNewOutgoingCall(Call call) {
 
-        // 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.
+        Preconditions.checkNotNull(mCallServices);
+        Preconditions.checkNotNull(mSelectors);
 
-        // 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
+        // Convert to (duplicate-free) list to aid index-based iteration, see the comment under
+        // setSelectors regarding using LinkedHashSet instead.
+        List<ICallServiceSelector> selectors = Lists.newArrayList();
+        selectors.addAll(mSelectors);
+
+        // Create the processor for this (outgoing) call and store it in a map such that call
+        // attempts can be aborted etc.
+        // TODO(gilad): Consider passing mSelector as an immutable set.
+        OutgoingCallProcessor processor =
+                new OutgoingCallProcessor(call, mCallServices, selectors, this);
+
+        outgoingCallProcessors.put(call, processor);
+        processor.process();
     }
 
     /**