DO NOT MERGE. Implement connection error dialogs (2/4)

Implement reporting of connection errors from ConnectionServices through
Telecomm to the InCallUI.

Bug: 15195720
Bug: 15117141
Change-Id: Idbd822a4eed897676663da008071c6050cc79616
(cherry picked from commit a3cb9e34db5952f0852c1a3230870cc6e378f246)
diff --git a/src/com/android/telecomm/AsyncResultCallback.java b/src/com/android/telecomm/AsyncResultCallback.java
index d059c6c..b6e0de5 100644
--- a/src/com/android/telecomm/AsyncResultCallback.java
+++ b/src/com/android/telecomm/AsyncResultCallback.java
@@ -20,5 +20,5 @@
  * Generic result interface for use with async method callback.
  */
 interface AsyncResultCallback<T> {
-    void onResult(T param1);
+    void onResult(T result, int errorCode, String errorMsg);
 }
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index cd64ff8..a699f02 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -55,7 +55,7 @@
      */
     interface Listener {
         void onSuccessfulOutgoingCall(Call call);
-        void onFailedOutgoingCall(Call call, boolean isAborted);
+        void onFailedOutgoingCall(Call call, boolean isAborted, int errorCode, String errorMsg);
         void onSuccessfulIncomingCall(Call call, CallInfo callInfo);
         void onFailedIncomingCall(Call call);
         void onRequestingRingback(Call call, boolean requestingRingback);
@@ -500,11 +500,12 @@
                 Switchboard.getInstance().getSelectorRepository(),
                 new AsyncResultCallback<Boolean>() {
                     @Override
-                    public void onResult(Boolean wasCallPlaced) {
+                    public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
                         if (wasCallPlaced) {
                             handleSuccessfulOutgoing();
                         } else {
-                            handleFailedOutgoing(mOutgoingCallProcessor.isAborted());
+                            handleFailedOutgoing(
+                                    mOutgoingCallProcessor.isAborted(), errorCode, errorMsg);
                         }
                         mOutgoingCallProcessor = null;
                     }
@@ -519,10 +520,10 @@
         }
     }
 
-    void handleFailedOutgoing(boolean isAborted) {
+    void handleFailedOutgoing(boolean isAborted, int errorCode, String errorMsg) {
         // TODO(santoscordon): Replace this with state transitions related to "connecting".
         for (Listener l : mListeners) {
-            l.onFailedOutgoingCall(this, isAborted);
+            l.onFailedOutgoingCall(this, isAborted, errorCode, errorMsg);
         }
 
         clearCallService();
diff --git a/src/com/android/telecomm/CallServiceSelectorWrapper.java b/src/com/android/telecomm/CallServiceSelectorWrapper.java
index 7cc01a2..6ee73d2 100644
--- a/src/com/android/telecomm/CallServiceSelectorWrapper.java
+++ b/src/com/android/telecomm/CallServiceSelectorWrapper.java
@@ -27,6 +27,7 @@
 import android.telecomm.CallInfo;
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.TelecommConstants;
+import android.telephony.DisconnectCause;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecomm.ICallServiceSelector;
@@ -61,7 +62,7 @@
                                         (List<CallServiceDescriptor>) args.arg2;
 
                                 mCallIdMapper.removeCall(callId);
-                                mPendingSelects.remove(callId).onResult(descriptors);
+                                mPendingSelects.remove(callId).onResult(descriptors, 0, null);
                             } else {
                                 Log.w(this, "setSelectedCallServices: unknown call: %s, id: %s",
                                         callId, args.arg1);
@@ -189,13 +190,14 @@
                     mSelectorInterface.select(callInfo, descriptors);
                 } catch (RemoteException e) {
                     mCallIdMapper.removeCall(call);
-                    mPendingSelects.get(callId).onResult(null);
+                    mPendingSelects.get(callId).onResult(
+                            null, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
                 }
             }
 
             @Override
             public void onFailure() {
-                resultCallback.onResult(null);
+                resultCallback.onResult(null, DisconnectCause.ERROR_UNSPECIFIED, null);
             }
         };
 
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 41a80e5..d09c52a 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -25,7 +25,9 @@
 import android.telecomm.CallInfo;
 import android.telecomm.CallService;
 import android.telecomm.CallServiceDescriptor;
+import android.telecomm.ConnectionRequest;
 import android.telecomm.TelecommConstants;
+import android.telephony.DisconnectCause;
 
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecomm.ICallService;
@@ -35,6 +37,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 
+import org.apache.http.conn.ClientConnectionRequest;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -80,7 +84,7 @@
                     case MSG_HANDLE_SUCCESSFUL_OUTGOING_CALL: {
                         String callId = (String) msg.obj;
                         if (mPendingOutgoingCalls.containsKey(callId)) {
-                            mPendingOutgoingCalls.remove(callId).onResult(true);
+                            mPendingOutgoingCalls.remove(callId).onResult(true, 0, null);
                         } else {
                             Log.w(this, "handleSuccessfulOutgoingCall, unknown call: %s", callId);
                         }
@@ -90,11 +94,13 @@
                         SomeArgs args = (SomeArgs) msg.obj;
                         try {
                             String callId = (String) args.arg1;
-                            String reason = (String) args.arg2;
+                            int statusCode = args.argi1;
+                            String statusMsg = (String) args.arg2;
                             // TODO(santoscordon): Do something with 'reason' or get rid of it.
 
                             if (mPendingOutgoingCalls.containsKey(callId)) {
-                                mPendingOutgoingCalls.remove(callId).onResult(false);
+                                mPendingOutgoingCalls.remove(callId).onResult(
+                                        false, statusCode, statusMsg);
                                 mCallIdMapper.removeCall(callId);
                             } else {
                                 Log.w(this, "handleFailedOutgoingCall, unknown call: %s", callId);
@@ -194,12 +200,16 @@
 
         /** {@inheritDoc} */
         @Override
-        public void handleFailedOutgoingCall(String callId, String reason) {
-            mCallIdMapper.checkValidCallId(callId);
-            Log.d(this, "handleFailedOutgoingCall %s", callId);
+        public void handleFailedOutgoingCall(
+                ConnectionRequest request,
+                int errorCode,
+                String errorMsg) {
+            mCallIdMapper.checkValidCallId(request.getCallId());
+            Log.d(this, "handleFailedOutgoingCall %s", request.getCallId());
             SomeArgs args = SomeArgs.obtain();
-            args.arg1 = callId;
-            args.arg2 = reason;
+            args.arg1 = request.getCallId();
+            args.argi1 = errorCode;
+            args.arg2 = errorMsg;
             mHandler.obtainMessage(MSG_HANDLE_FAILED_OUTGOING_CALL, args).sendToTarget();
         }
 
@@ -325,13 +335,14 @@
                     CallInfo callInfo = call.toCallInfo(callId);
                     mServiceInterface.call(callInfo);
                 } catch (RemoteException e) {
-                    mPendingOutgoingCalls.remove(callId).onResult(false);
+                    mPendingOutgoingCalls.remove(callId).onResult(
+                            false, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
                 }
             }
 
             @Override
             public void onFailure() {
-                resultCallback.onResult(false);
+                resultCallback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
             }
         };
 
@@ -487,7 +498,7 @@
         AsyncResultCallback<Boolean> outgoingResultCallback =
             mPendingOutgoingCalls.remove(mCallIdMapper.getCallId(call));
         if (outgoingResultCallback != null) {
-            outgoingResultCallback.onResult(false);
+            outgoingResultCallback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
         }
 
         mCallIdMapper.removeCall(call);
@@ -516,7 +527,7 @@
     private void handleCallServiceDeath() {
         if (!mPendingOutgoingCalls.isEmpty()) {
             for (AsyncResultCallback<Boolean> callback : mPendingOutgoingCalls.values()) {
-                callback.onResult(false);
+                callback.onResult(false, DisconnectCause.ERROR_UNSPECIFIED, null);
             }
             mPendingOutgoingCalls.clear();
         }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index a2aa045..819ed34 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -140,14 +140,14 @@
     }
 
     @Override
-    public void onFailedOutgoingCall(Call call, boolean isAborted) {
+    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, DisconnectCause.ERROR_UNSPECIFIED, null);
+            markCallAsDisconnected(call, errorCode, errorMsg);
         }
     }
 
diff --git a/src/com/android/telecomm/InCallController.java b/src/com/android/telecomm/InCallController.java
index a8d54fa..1318825 100644
--- a/src/com/android/telecomm/InCallController.java
+++ b/src/com/android/telecomm/InCallController.java
@@ -257,8 +257,8 @@
         if (state == CallState.DISCONNECTED && call.getHandoffCallServiceDescriptor() != null) {
             state = CallState.ACTIVE;
         }
-        return new InCallCall(callId, state, call.getDisconnectCause(), capabilities,
-                call.getConnectTimeMillis(), call.getHandle(), call.getGatewayInfo(), descriptor,
-                call.getHandoffCallServiceDescriptor());
+        return new InCallCall(callId, state, call.getDisconnectCause(), call.getDisconnectMessage(),
+                capabilities, call.getConnectTimeMillis(), call.getHandle(), call.getGatewayInfo(),
+                descriptor, call.getHandoffCallServiceDescriptor());
     }
 }
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 0f77567..366307c 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -20,6 +20,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.telecomm.CallServiceDescriptor;
+import android.telephony.DisconnectCause;
 
 import com.android.telecomm.BaseRepository.LookupCallback;
 import com.google.android.collect.Sets;
@@ -103,6 +104,10 @@
 
     private boolean mIsAborted = false;
 
+    private int mLastErrorCode = 0;
+
+    private String mLastErrorMsg = null;
+
     /**
      * 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-
@@ -182,7 +187,9 @@
                 callService.abort(mCall);
             }
 
-            sendResult(false);
+            // We consider a deliberate abort to be a "normal" disconnect, not
+            // requiring special reporting.
+            sendResult(false, DisconnectCause.LOCAL, null);
         }
     }
 
@@ -205,17 +212,22 @@
             return;
         }
 
-        sendResult(true);
+        sendResult(true, DisconnectCause.NOT_DISCONNECTED, null);
     }
 
     /**
      * Attempts the next call service if the specified call service is the one currently being
      * attempted.
      *
-     * @param reason The call-service supplied reason for the failed call attempt.
+     * @param errorCode The reason for the failure, one of {@link DisconnectCause}.
+     * @param errorMsg Optional text reason for the failure.
      */
-    void handleFailedCallAttempt(String reason) {
-        Log.v(this, "handleFailedCallAttempt");
+    void handleFailedCallAttempt(int errorCode, String errorMsg) {
+        Log.v(this, "handleFailedCallAttempt %s %s", DisconnectCause.toString(errorCode), errorMsg);
+        // Store latest error code and message. If this is our last available attempt at placing
+        // a call, these error details will be considered "the" cause of the failure.
+        mLastErrorCode = errorCode;
+        mLastErrorMsg = errorMsg;
         if (!mIsAborted) {
             ThreadUtil.checkOnMainThread();
             attemptNextCallService();
@@ -327,14 +339,16 @@
             selector.select(mCall, mCallServiceDescriptors,
                     new AsyncResultCallback<List<CallServiceDescriptor>>() {
                         @Override
-                        public void onResult(List<CallServiceDescriptor> descriptors) {
+                        public void onResult(
+                                List<CallServiceDescriptor> descriptors,
+                                int errorCode, String errorMsg) {
                             processSelectedCallServices(descriptors);
                         }
                     });
         } else {
             Log.v(this, "attemptNextSelector, no more selectors, failing");
             mCall.clearCallServiceSelector();
-            sendResult(false);
+            sendResult(false, mLastErrorCode, mLastErrorMsg);
         }
     }
 
@@ -369,11 +383,11 @@
 
                 callService.call(mCall, new AsyncResultCallback<Boolean>() {
                     @Override
-                    public void onResult(Boolean wasCallPlaced) {
+                    public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
                         if (wasCallPlaced) {
                             handleSuccessfulCallAttempt(callService);
                         } else {
-                            handleFailedCallAttempt("call failed.");
+                            handleFailedCallAttempt(errorCode, errorMsg);
                         }
 
                         // If successful, the call should not have it's own association to keep
@@ -389,9 +403,9 @@
         }
     }
 
-    private void sendResult(boolean wasCallPlaced) {
+    private void sendResult(boolean wasCallPlaced, int errorCode, String errorMsg) {
         if (mResultCallback != null) {
-            mResultCallback.onResult(wasCallPlaced);
+            mResultCallback.onResult(wasCallPlaced, errorCode, errorMsg);
             mResultCallback = null;
 
             mHandler.removeMessages(MSG_EXPIRE);