Implement cancelOutgoingCall

Change-Id: I9e5da9c607675bc3c230f6eb6d1cc149a38bf905
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index a6ec347..1169d5a 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -53,14 +53,15 @@
  *  from the time the call intent was received by Telecomm (vs. the time the call was
  *  connected etc).
  */
-final class Call {
+final class Call implements OutgoingCallResponse {
 
     /**
      * Listener for events on the call.
      */
     interface Listener {
         void onSuccessfulOutgoingCall(Call call);
-        void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg);
+        void onFailedOutgoingCall(Call call, int errorCode, String errorMsg);
+        void onCancelledOutgoingCall(Call call);
         void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
         void onFailedIncomingCall(Call call);
         void onRequestingRingback(Call call, boolean requestingRingback);
@@ -524,37 +525,39 @@
         Preconditions.checkState(mOutgoingCallProcessor == null);
 
         mOutgoingCallProcessor = new OutgoingCallProcessor(
-                this,
-                Switchboard.getInstance().getCallServiceRepository(),
-                new AsyncResultCallback<Boolean>() {
-                    @Override
-                    public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
-                        if (wasCallPlaced) {
-                            handleSuccessfulOutgoing();
-                        } else {
-                            handleFailedOutgoing(
-                                    mOutgoingCallProcessor.isAborted(), errorCode, errorMsg);
-                        }
-                        mOutgoingCallProcessor = null;
-                    }
-                });
+                this, Switchboard.getInstance().getCallServiceRepository(), this);
         mOutgoingCallProcessor.process();
     }
 
-    void handleSuccessfulOutgoing() {
+    @Override
+    public void onOutgoingCallSuccess() {
         // TODO(santoscordon): Replace this with state transitions related to "connecting".
         for (Listener l : mListeners) {
             l.onSuccessfulOutgoingCall(this);
         }
+        mOutgoingCallProcessor = null;
     }
 
-    void handleFailedOutgoing(boolean isAborted, int errorCode, String errorMsg) {
+    @Override
+    public void onOutgoingCallFailure(int code, String msg) {
         // TODO(santoscordon): Replace this with state transitions related to "connecting".
         for (Listener l : mListeners) {
-            l.onFailedOutgoingCall(this, isAborted, errorCode, errorMsg);
+            l.onFailedOutgoingCall(this, code, msg);
         }
 
         clearCallService();
+        mOutgoingCallProcessor = null;
+    }
+
+    @Override
+    public void onOutgoingCallCancel() {
+        // TODO(santoscordon): Replace this with state transitions related to "connecting".
+        for (Listener l : mListeners) {
+            l.onCancelledOutgoingCall(this);
+        }
+
+        clearCallService();
+        mOutgoingCallProcessor = null;
     }
 
     /**
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 5d7b47a..782f7a4 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -61,18 +61,19 @@
         private static final int MSG_NOTIFY_INCOMING_CALL = 1;
         private static final int MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL = 2;
         private static final int MSG_HANDLE_FAILED_OUTGOING_CALL = 3;
-        private static final int MSG_SET_ACTIVE = 4;
-        private static final int MSG_SET_RINGING = 5;
-        private static final int MSG_SET_DIALING = 6;
-        private static final int MSG_SET_DISCONNECTED = 7;
-        private static final int MSG_SET_ON_HOLD = 8;
-        private static final int MSG_SET_REQUESTING_RINGBACK = 9;
-        private static final int MSG_ON_POST_DIAL_WAIT = 10;
-        private static final int MSG_CAN_CONFERENCE = 11;
-        private static final int MSG_SET_IS_CONFERENCED = 12;
-        private static final int MSG_ADD_CONFERENCE_CALL = 13;
-        private static final int MSG_HANDOFF_CALL = 14;
-        private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 15;
+        private static final int MSG_CANCEL_OUTGOING_CALL = 4;
+        private static final int MSG_SET_ACTIVE = 5;
+        private static final int MSG_SET_RINGING = 6;
+        private static final int MSG_SET_DIALING = 7;
+        private static final int MSG_SET_DISCONNECTED = 8;
+        private static final int MSG_SET_ON_HOLD = 9;
+        private static final int MSG_SET_REQUESTING_RINGBACK = 10;
+        private static final int MSG_ON_POST_DIAL_WAIT = 11;
+        private static final int MSG_CAN_CONFERENCE = 12;
+        private static final int MSG_SET_IS_CONFERENCED = 13;
+        private static final int MSG_ADD_CONFERENCE_CALL = 14;
+        private static final int MSG_HANDOFF_CALL = 15;
+        private static final int MSG_QUERY_REMOTE_CALL_SERVICES = 16;
 
         private final Handler mHandler = new Handler() {
             @Override
@@ -100,7 +101,7 @@
                     case MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL: {
                         String callId = (String) msg.obj;
                         if (mPendingOutgoingCalls.containsKey(callId)) {
-                            mPendingOutgoingCalls.remove(callId).onResult(true, 0, null);
+                            mPendingOutgoingCalls.remove(callId).onOutgoingCallSuccess();
                         } else {
                             //Log.w(this, "handleSuccessfulOutgoingCall, unknown call: %s", callId);
                         }
@@ -115,8 +116,8 @@
                             // TODO(santoscordon): Do something with 'reason' or get rid of it.
 
                             if (mPendingOutgoingCalls.containsKey(callId)) {
-                                mPendingOutgoingCalls.remove(callId).onResult(
-                                        false, statusCode, statusMsg);
+                                mPendingOutgoingCalls.remove(callId).onOutgoingCallFailure(
+                                        statusCode, statusMsg);
                                 mCallIdMapper.removeCall(callId);
                             } else {
                                 //Log.w(this, "handleFailedOutgoingCall, unknown call: %s", callId);
@@ -126,6 +127,15 @@
                         }
                         break;
                     }
+                    case MSG_CANCEL_OUTGOING_CALL: {
+                        String callId = (String) msg.obj;
+                        if (mPendingOutgoingCalls.containsKey(callId)) {
+                            mPendingOutgoingCalls.remove(callId).onOutgoingCallCancel();
+                        } else {
+                            //Log.w(this, "cancelOutgoingCall, unknown call: %s", callId);
+                        }
+                        break;
+                    }
                     case MSG_SET_ACTIVE:
                         call = mCallIdMapper.getCall(msg.obj);
                         if (call != null) {
@@ -306,6 +316,14 @@
 
         /** {@inheritDoc} */
         @Override
+        public void cancelOutgoingCall(String callId) {
+            logIncoming("cancelOutgoingCall %s", callId);
+            mCallIdMapper.checkValidCallId(callId);
+            mHandler.obtainMessage(MSG_CANCEL_OUTGOING_CALL, callId).sendToTarget();
+        }
+
+        /** {@inheritDoc} */
+        @Override
         public void setActive(String callId) {
             logIncoming("setActive %s", callId);
             mCallIdMapper.checkValidCallId(callId);
@@ -428,7 +446,7 @@
     private final CallServiceDescriptor mDescriptor;
     private final CallIdMapper mCallIdMapper = new CallIdMapper("CallService");
     private final IncomingCallsManager mIncomingCallsManager;
-    private final Map<String, AsyncResultCallback<Boolean>> mPendingOutgoingCalls = new HashMap<>();
+    private final Map<String, OutgoingCallResponse> mPendingOutgoingCalls = new HashMap<>();
     private final Handler mHandler = new Handler();
 
     private Binder mBinder = new Binder();
@@ -472,13 +490,13 @@
      * Attempts to place the specified call, see {@link ICallService#call}. Returns the result
      * asynchronously through the specified callback.
      */
-    void call(final Call call, final AsyncResultCallback<Boolean> resultCallback) {
+    void call(final Call call, final OutgoingCallResponse callResponse) {
         Log.d(this, "call(%s) via %s.", call, getComponentName());
         BindCallback callback = new BindCallback() {
             @Override
             public void onSuccess() {
                 String callId = mCallIdMapper.getCallId(call);
-                mPendingOutgoingCalls.put(callId, resultCallback);
+                mPendingOutgoingCalls.put(callId, callResponse);
 
                 try {
                     CallInfo callInfo = call.toCallInfo(callId);
@@ -486,15 +504,15 @@
                     mServiceInterface.call(callInfo);
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to call -- %s", getDescriptor());
-                    mPendingOutgoingCalls.remove(callId).onResult(
-                            false, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
+                    mPendingOutgoingCalls.remove(callId).onOutgoingCallFailure(
+                            DisconnectCause.ERROR_UNSPECIFIED, e.toString());
                 }
             }
 
             @Override
             public void onFailure() {
                 Log.e(this, new Exception(), "Failure to call %s", getDescriptor());
-                resultCallback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
+                callResponse.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
             }
         };
 
@@ -661,10 +679,10 @@
     void removeCall(Call call) {
         mPendingIncomingCalls.remove(call);
 
-        AsyncResultCallback<Boolean> outgoingResultCallback =
-            mPendingOutgoingCalls.remove(mCallIdMapper.getCallId(call));
+        OutgoingCallResponse outgoingResultCallback =
+                mPendingOutgoingCalls.remove(mCallIdMapper.getCallId(call));
         if (outgoingResultCallback != null) {
-            outgoingResultCallback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
+            outgoingResultCallback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
         }
 
         mCallIdMapper.removeCall(call);
@@ -737,8 +755,8 @@
      */
     private void handleCallServiceDeath() {
         if (!mPendingOutgoingCalls.isEmpty()) {
-            for (AsyncResultCallback<Boolean> callback : mPendingOutgoingCalls.values()) {
-                callback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
+            for (OutgoingCallResponse callback : mPendingOutgoingCalls.values()) {
+                callback.onOutgoingCallFailure(DisconnectCause.ERROR_UNSPECIFIED, null);
             }
             mPendingOutgoingCalls.clear();
         }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index d5ddedc..174e59f 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -145,15 +145,17 @@
     }
 
     @Override
-    public void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg) {
-        Log.v(this, "onFailedOutgoingCall, call: %s, isAborted: %b", call, isAborted);
-        if (isAborted) {
-            setCallState(call, CallState.ABORTED);
-            removeCall(call);
-        } else {
-            // TODO: Replace disconnect cause with more specific disconnect causes.
-            markCallAsDisconnected(call, errorCode, errorMsg);
-        }
+    public void onFailedOutgoingCall(Call call, int errorCode, String errorMsg) {
+        Log.v(this, "onFailedOutgoingCall, call: %s", call);
+        // TODO: Replace disconnect cause with more specific disconnect causes.
+        markCallAsDisconnected(call, errorCode, errorMsg);
+    }
+
+    @Override
+    public void onCancelledOutgoingCall(Call call) {
+        Log.v(this, "onCancelledOutgoingCall, call: %s", call);
+        setCallState(call, CallState.ABORTED);
+        removeCall(call);
     }
 
     @Override
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 4531455..ffcfe6d 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -91,7 +91,7 @@
      */
     private Iterator<CallServiceDescriptor> mCallServiceDescriptorIterator;
 
-    private AsyncResultCallback<Boolean> mResultCallback;
+    private OutgoingCallResponse mResultCallback;
 
     private boolean mIsAborted = false;
 
@@ -111,7 +111,7 @@
     OutgoingCallProcessor(
             Call call,
             CallServiceRepository callServiceRepository,
-            AsyncResultCallback<Boolean> resultCallback) {
+            OutgoingCallResponse resultCallback) {
 
         ThreadUtil.checkOnMainThread();
 
@@ -263,17 +263,22 @@
                 callService.incrementAssociatedCallCount();
 
                 Log.i(this, "Attempting to call from %s", callService.getDescriptor());
-                callService.call(mCall, new AsyncResultCallback<Boolean>() {
+                callService.call(mCall, new OutgoingCallResponse() {
                     @Override
-                    public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
-                        if (wasCallPlaced) {
-                            handleSuccessfulCallAttempt(callService);
-                        } else {
-                            handleFailedCallAttempt(errorCode, errorMsg);
-                        }
+                    public void onOutgoingCallSuccess() {
+                        handleSuccessfulCallAttempt(callService);
+                        callService.decrementAssociatedCallCount();
+                    }
 
-                        // If successful, the call should not have it's own association to keep
-                        // the call service bound.
+                    @Override
+                    public void onOutgoingCallFailure(int code, String msg) {
+                        handleFailedCallAttempt(code, msg);
+                        callService.decrementAssociatedCallCount();
+                    }
+
+                    @Override
+                    public void onOutgoingCallCancel() {
+                        abort();
                         callService.decrementAssociatedCallCount();
                     }
                 });
@@ -288,7 +293,13 @@
 
     private void sendResult(boolean wasCallPlaced, int errorCode, String errorMsg) {
         if (mResultCallback != null) {
-            mResultCallback.onResult(wasCallPlaced, errorCode, errorMsg);
+            if (mIsAborted) {
+                mResultCallback.onOutgoingCallCancel();
+            } else if (wasCallPlaced) {
+                mResultCallback.onOutgoingCallSuccess();
+            } else {
+                mResultCallback.onOutgoingCallFailure(errorCode, errorMsg);
+            }
             mResultCallback = null;
 
             mHandler.removeMessages(MSG_EXPIRE);
diff --git a/src/com/android/telecomm/OutgoingCallResponse.java b/src/com/android/telecomm/OutgoingCallResponse.java
new file mode 100644
index 0000000..3d5c393
--- /dev/null
+++ b/src/com/android/telecomm/OutgoingCallResponse.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * A callback for providing the result of placing an outgoing call.
+ */
+interface OutgoingCallResponse {
+    void onOutgoingCallSuccess();
+    void onOutgoingCallFailure(int code, String msg);
+    void onOutgoingCallCancel();
+}