Making CallsManager interact with only Calls.

Eventually, I would like CallsManager to interact only with Call
objects.  This is one move in that direction.

OLD:
CallsManager->Switchboard-+->Call-+->CallService
                          +-------+

NEW:
CallsManager->Call-+->Switchboard-+->CallService
                   +--------------+

This change also includes new direct listening to Call events by
CallsManager. At the moment includes only success/failure of incoming
and outgoing calls but should eventually be more generic as we implement
more of the Telecomm design.

Change-Id: I7a764706acd0872960642a7b16c71a2bc514f3b3
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index e21f3de..fe984cd 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -22,6 +22,7 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.CallState;
 import android.telecomm.GatewayInfo;
+import android.telecomm.TelecommConstants;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 
@@ -37,6 +38,17 @@
  *  connected etc).
  */
 final class Call {
+
+    /**
+     * Listener for events on the call.
+     */
+    interface Listener {
+        void onSuccessfulOutgoingCall(Call call);
+        void onFailedOutgoingCall(Call call, boolean isAborted);
+        void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
+        void onFailedIncomingCall(Call call);
+    }
+
     /** Additional contact information beyond handle above, optional. */
     private final ContactInfo mContactInfo;
 
@@ -112,6 +124,9 @@
      */
     private CallServiceDescriptor mHandoffCallServiceDescriptor;
 
+    /** Set of listeners on this call. */
+    private Set<Listener> mListeners = Sets.newHashSet();
+
     /**
      * Creates an empty call object.
      *
@@ -137,6 +152,14 @@
         mIsIncoming = isIncoming;
     }
 
+    void addListener(Listener listener) {
+        mListeners.add(listener);
+    }
+
+    void removeListener(Listener listener) {
+        mListeners.remove(listener);
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         String component = null;
@@ -318,6 +341,66 @@
     }
 
     /**
+     * Starts the incoming call flow through the switchboard. When switchboard completes, it will
+     * invoke handle[Un]SuccessfulIncomingCall.
+     *
+     * @param descriptor The relevant call-service descriptor.
+     * @param extras The optional extras passed via
+     *         {@link TelecommConstants#EXTRA_INCOMING_CALL_EXTRAS}.
+     */
+    void startIncoming(CallServiceDescriptor descriptor, Bundle extras) {
+        Switchboard.getInstance().retrieveIncomingCall(this, descriptor, extras);
+    }
+
+    void handleSuccessfulIncoming(CallInfo callInfo) {
+        Preconditions.checkState(callInfo.getState() == CallState.RINGING);
+        setHandle(callInfo.getHandle());
+
+        // TODO(santoscordon): Make this class (not CallsManager) responsible for changing the call
+        // state to RINGING.
+
+        // TODO(santoscordon): Replace this with state transitions related to "connecting".
+        for (Listener l : mListeners) {
+            l.onSuccessfulIncomingCall(this, callInfo);
+        }
+    }
+
+    void handleFailedIncoming() {
+        clearCallService();
+
+        // TODO: Needs more specific disconnect error for this case.
+        setDisconnectCause(DisconnectCause.ERROR_UNSPECIFIED, null);
+        setState(CallState.DISCONNECTED);
+
+        // TODO(santoscordon): Replace this with state transitions related to "connecting".
+        for (Listener l : mListeners) {
+            l.onFailedIncomingCall(this);
+        }
+    }
+
+    /**
+     * Starts the outgoing call flow through the switchboard. When switchboard completes, it will
+     * invoke handleSuccessful/FailedOutgoingCall.
+     */
+    void startOutgoing() {
+        Switchboard.getInstance().placeOutgoingCall(this);
+    }
+
+    void handleSuccessfulOutgoing() {
+        // TODO(santoscordon): Replace this with state transitions related to "connecting".
+        for (Listener l : mListeners) {
+            l.onSuccessfulOutgoingCall(this);
+        }
+    }
+
+    void handleFailedOutgoing(boolean isAborted) {
+        // TODO(santoscordon): Replace this with state transitions related to "connecting".
+        for (Listener l : mListeners) {
+            l.onFailedOutgoingCall(this, isAborted);
+        }
+    }
+
+    /**
      * Adds the specified call service to the list of incompatible services.  The set is used when
      * attempting to switch a phone call between call services such that incompatible services can
      * be avoided.
@@ -344,17 +427,17 @@
     }
 
     /**
-     * Aborts ongoing attempts to connect this call. Only applicable to {@link CallState#NEW}
-     * outgoing calls.  See {@link #disconnect} for already-connected calls.
+     * Issues an abort signal to the associated call service and clears the current call service
+     * and call-service selector.
      */
-    void abort() {
-        if (mState == CallState.NEW) {
-            if (mCallService != null) {
-                mCallService.abort(this);
-            }
-            clearCallService();
-            clearCallServiceSelector();
+    void finalizeAbort() {
+        Preconditions.checkState(mState == CallState.NEW);
+
+        if (mCallService != null) {
+            mCallService.abort(this);
         }
+        clearCallService();
+        clearCallServiceSelector();
     }
 
     /**
@@ -385,11 +468,20 @@
      * Attempts to disconnect the call through the call service.
      */
     void disconnect() {
-        if (mCallService == null) {
-            Log.d(this, "disconnect() request on a call without a call service.");
-            // In case this call is ringing, ensure we abort and clean up right away.
-            CallsManager.getInstance().abortCall(this);
+        if (mState == CallState.NEW) {
+            // There is a very strange indirection here. When we are told to disconnect, we issue
+            // an 'abort' to the switchboard if we are still in the NEW (or "connecting") state.
+            // The switchboard will then cancel the outgoing call process and ask this class to
+            // finalize the abort procedure via {@link #finalizeAbort}. The issue is that
+            // Switchboard edits the state of the call as part of the process and then this class
+            // is responsible for undoing it, and that is all kinds of backwards.
+            // TODO(santoscordon): Remove Switchboard's requirement to edit the state of the Call
+            // objects and remove any multi-class shared state of incoming and outgoing call
+            // processing.
+            Switchboard.getInstance().abortCall(this);
         } else {
+            Preconditions.checkNotNull(mCallService);
+
             Log.i(this, "Send disconnect to call service for call: %s", this);
             // The call isn't officially disconnected until the call service confirms that the call
             // was actually disconnected. Only then is the association between call and call service