Wiring up the Telecomm abort flow.

Change-Id: Ie9de03507fabe09c09347b37355c8fe5ce4492b0
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 2babb4e..f40d4ea 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -169,14 +169,14 @@
     }
 
     /**
-     * Aborts ongoing attempts to connect this call. No-op once the call is connected or has been
-     * disconnected.  See {@link #disconnect} for already-connected calls.
+     * Aborts ongoing attempts to connect this call. Only applicable to {@link CallState#NEW}
+     * outgoing calls.  See {@link #disconnect} for already-connected calls.
      */
     void abort() {
-        if (mState == CallState.NEW ||
-                mState == CallState.DIALING ||
-                mState == CallState.RINGING) {
-
+        if (mState == CallState.NEW) {
+            if (mCallService != null) {
+                mCallService.abort(mId);
+            }
             clearCallService();
             clearCallServiceSelector();
             mState = CallState.ABORTED;
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index f602d50..8daa16e 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -46,9 +46,16 @@
     /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
-    /** The set of pending incoming call IDs. Contains the call IDs for which we are expecting
-     * details via {@link #handleIncomingCall}. If {@link #handleIncomingCall} is invoked for a call
-     * ID that is not in this set, it will be ignored.
+    /**
+     * The set of pending outgoing call IDs.  Any {@link #handleSuccessfulOutgoingCall} and
+     * {@link #handleFailedOutgoingCall} invocations with a call ID that is not in this set
+     * are ignored.
+     */
+    private final Set<String> mPendingOutgoingCallIds = Sets.newHashSet();
+
+    /**
+     * The set of pending incoming call IDs.  Any {@link #handleIncomingCall} invocations with
+     * a call ID not in this set are ignored.
      */
     private final Set<String> mPendingIncomingCallIds = Sets.newHashSet();
 
@@ -80,7 +87,7 @@
                     mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo);
                 } else {
                     Log.wtf(CallServiceAdapter.this,
-                            "Received details for an unknown incoming call %s", callInfo);
+                            "Unknown incoming call: %s", callInfo);
                 }
             }
         });
@@ -91,7 +98,13 @@
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
             @Override public void run() {
-                mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
+                if (mPendingOutgoingCallIds.remove(callId)) {
+                    mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
+                } else {
+                    // TODO(gilad): Figure out how to wire up the callService.abort() call.
+                    Log.wtf(CallServiceAdapter.this,
+                            "Unknown outgoing call: %s", callId);
+                }
             }
         });
     }
@@ -101,7 +114,12 @@
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
             @Override public void run() {
-                mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
+                if (mPendingOutgoingCallIds.remove(callId)) {
+                    mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
+                } else {
+                    Log.wtf(CallServiceAdapter.this,
+                            "Unknown outgoing call: %s", callId);
+                }
             }
         });
     }
@@ -137,6 +155,8 @@
     }
 
     /** {@inheritDoc} */
+    // TODO(gilad): Ensure that any communication from the underlying ICallService
+    // implementation is expected (or otherwise suppressed at the adapter level).
     @Override public void setDisconnected(final String callId) {
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
@@ -147,6 +167,26 @@
     }
 
     /**
+     * Adds the specified call ID to the list of pending outgoing call IDs.
+     * TODO(gilad): Consider passing the call processor (instead of the ID) both here and in the
+     * remove case (same for incoming) such that the detour via the *CallsManager can be avoided.
+     *
+     * @param callId The ID of the call.
+     */
+    void addPendingOutgoingCallId(String callId) {
+        mPendingOutgoingCallIds.add(callId);
+    }
+
+    /**
+     * Removes the specified call ID from the list of pending outgoing call IDs.
+     *
+     * @param callId The ID of the call.
+     */
+    void removePendingOutgoingCallId(String callId) {
+        mPendingOutgoingCallIds.remove(callId);
+    }
+
+    /**
      * Adds a call ID to the list of pending incoming call IDs. Only calls with call IDs in the
      * list will be handled by {@link #handleIncomingCall}.
      *
@@ -157,7 +197,7 @@
     }
 
     /**
-     * Removed a call ID from the list of pending incoming call IDs.
+     * Removes the specified call ID from the list of pending incoming call IDs.
      *
      * @param callId The ID of the call.
      */
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 94be7c8..15ec20d 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -24,6 +24,7 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.ICallService;
 import android.telecomm.ICallServiceAdapter;
+import android.telecomm.ICallServiceProvider;
 
 /**
  * Wrapper for {@link ICallService}s, handles binding to {@link ICallService} and keeps track of
@@ -92,11 +93,25 @@
 
     /** See {@link ICallService#call}. */
     public void call(CallInfo callInfo) {
+        String callId = callInfo.getId();
         if (isServiceValid("call")) {
             try {
                 mServiceInterface.call(callInfo);
+                mAdapter.addPendingOutgoingCallId(callId);
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to place call " + callInfo.getId() + ".");
+                Log.e(this, e, "Failed to place call " + callId + ".");
+            }
+        }
+    }
+
+    /** See {@link ICallService#abort}. */
+    public void abort(String callId) {
+        mAdapter.removePendingOutgoingCallId(callId);
+        if (isServiceValid("abort")) {
+            try {
+                mServiceInterface.abort(callId);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to abort call %s", callId);
             }
         }
     }
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 42a16c6..4d21280 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -68,7 +68,7 @@
     private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
 
     /**
-     * The set of currently-available call-service selector implementations.
+     * The list of currently-available call-service selector implementations.
      */
     private final List<ICallServiceSelector> mSelectors;
 
@@ -111,9 +111,6 @@
 
         ThreadUtil.checkOnMainThread();
 
-        Preconditions.checkNotNull(callServices);
-        Preconditions.checkNotNull(selectors);
-
         mCall = call;
         mSelectors = selectors;
         mOutgoingCallsManager = outgoingCallsManager;
@@ -132,17 +129,14 @@
      * Initiates the attempt to place the call.  No-op beyond the first invocation.
      */
     void process() {
-        ThreadUtil.checkOnMainThread();
+        if (!mIsAborted) {
+            // Only process un-aborted calls.
+            ThreadUtil.checkOnMainThread();
 
-        if (mSelectors.isEmpty() || mCallServiceDescriptors.isEmpty()) {
-            // TODO(gilad): Consider adding a failure message/type to differentiate the various
-            // cases, or potentially throw an exception in this case.
-            // TODO(gilad): Perform this check all the way up in switchboard to short-circuit
-            // the current detour.
-            mOutgoingCallsManager.handleFailedOutgoingCall(mCall);
-        } else if (mSelectorIterator == null) {
-            mSelectorIterator = mSelectors.iterator();
-            attemptNextSelector();
+            if (mSelectorIterator == null) {
+                mSelectorIterator = mSelectors.iterator();
+                attemptNextSelector();
+            }
         }
     }
 
@@ -154,9 +148,6 @@
         ThreadUtil.checkOnMainThread();
         if (!mIsAborted) {
             mCall.abort();
-
-            // TODO(gilad): Add logic to notify the relevant call service and/or selector.
-
             mIsAborted = true;
         }
     }
@@ -185,11 +176,13 @@
      * @param reason The call-service supplied reason for the failed call attempt.
      */
     void handleFailedCallAttempt(String reason) {
-        ThreadUtil.checkOnMainThread();
+        if (!mIsAborted) {
+            ThreadUtil.checkOnMainThread();
 
-        mCall.clearCallService();
-        mCall.clearCallServiceSelector();
-        attemptNextCallService();
+            mCall.clearCallService();
+            mCall.clearCallServiceSelector();
+            attemptNextCallService();
+        }
     }
 
     /**