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

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

Bug: 15195720
Bug: 15117141
Change-Id: Ia8cd6f2092661225c5cc65a60abba1c35e8fba65
diff --git a/src/com/android/services/telephony/PstnConnectionService.java b/src/com/android/services/telephony/PstnConnectionService.java
index cd3598e..8ac6ee9 100644
--- a/src/com/android/services/telephony/PstnConnectionService.java
+++ b/src/com/android/services/telephony/PstnConnectionService.java
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 
+import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 
 import com.android.internal.telephony.Call;
@@ -52,7 +53,11 @@
 
         if (!canCall(request.getHandle())) {
             Log.d(this, "Cannot place the call with %s", this.getClass().getSimpleName());
-            respondWithError(request, response, "Cannot place call.");
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // TODO: Code for "ConnSvc cannot call"
+                    "Cannot place call.");
             return;
         }
 
@@ -71,7 +76,11 @@
                         if (isRadioReady) {
                             startCallWithPhone(phone, request, response);
                         } else {
-                            respondWithError(request, response, "Failed to turn on radio.");
+                            respondWithError(
+                                    request,
+                                    response,
+                                    DisconnectCause.POWER_OFF,
+                                    "Failed to turn on radio.");
                         }
                     }
                 }
@@ -103,6 +112,7 @@
                 respondWithError(
                         request,
                         response,
+                        DisconnectCause.ERROR_UNSPECIFIED,  // Internal error
                         "Cannot set incoming call ID, ringing connection already registered.");
             } else {
                 // Address can be null for blocked calls.
@@ -117,7 +127,11 @@
                 try {
                     telephonyConnection = createTelephonyConnection(request, connection);
                 } catch (Exception e) {
-                    respondWithError(request, response, e.getMessage());
+                    respondWithError(
+                            request,
+                            response,
+                            DisconnectCause.ERROR_UNSPECIFIED,  // Internal error
+                            e.getMessage());
                     return;
                 }
 
@@ -130,6 +144,7 @@
             respondWithError(
                     request,
                     response,
+                    DisconnectCause.INCOMING_MISSED,  // Most likely cause
                     String.format("Found no ringing call, call state: %s", call.getState()));
         }
         super.onCreateIncomingConnection(request, response);
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 185fa57..c17b8f6 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -17,10 +17,13 @@
 package com.android.services.telephony;
 
 import android.net.Uri;
+import android.telephony.DisconnectCause;
+import android.telephony.ServiceState;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
+
 import android.telecomm.Connection;
 import android.telecomm.ConnectionRequest;
 import android.telecomm.ConnectionService;
@@ -46,7 +49,11 @@
         try {
             respondWithResult(handle, response, canCall(handle) ? new Subscription() : null);
         } catch (Exception e) {
-            respondWithError(handle, response, "onFindSubscriptions error: " + e.toString());
+            respondWithError(
+                    handle,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // Internal error
+                    "onFindSubscriptions error: " + e.toString());
         }
     }
 
@@ -64,18 +71,34 @@
         Log.d(this, "startCallWithPhone: %s.", request);
 
         if (phone == null) {
-            respondWithError(request, response, "Phone is null");
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
+                    "Phone is null");
             return;
         }
 
         if (request.getHandle() == null) {
-            respondWithError(request, response, "Handle is null");
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
+                    "Handle is null");
             return;
         }
 
         String number = request.getHandle().getSchemeSpecificPart();
         if (TextUtils.isEmpty(number)) {
-            respondWithError(request, response, "Unable to parse number");
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.INVALID_NUMBER,
+                    "Unable to parse number");
+            return;
+        }
+
+        if (!checkServiceStateForOutgoingCall(phone, request, response)) {
             return;
         }
 
@@ -84,12 +107,20 @@
             connection = phone.dial(number);
         } catch (CallStateException e) {
             Log.e(this, e, "Call to Phone.dial failed with exception");
-            respondWithError(request, response, e.getMessage());
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
+                    e.getMessage());
             return;
         }
 
         if (connection == null) {
-            respondWithError(request, response, "Call to phone.dial failed");
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
+                    "Call to phone.dial failed");
             return;
         }
 
@@ -97,16 +128,63 @@
             respondWithResult(request, response, createTelephonyConnection(request, connection));
         } catch (Exception e) {
             Log.e(this, e, "Call to createConnection failed with exception");
-            respondWithError(request, response, e.getMessage());
+            respondWithError(
+                    request,
+                    response,
+                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
+                    e.getMessage());
         }
     }
 
+    private boolean checkServiceStateForOutgoingCall(
+            Phone phone,
+            ConnectionRequest request,
+            Response<ConnectionRequest, Connection> response) {
+        int state = phone.getServiceState().getState();
+        switch (state) {
+            case ServiceState.STATE_IN_SERVICE:
+                return true;
+            case ServiceState.STATE_OUT_OF_SERVICE:
+                respondWithError(
+                        request,
+                        response,
+                        DisconnectCause.OUT_OF_SERVICE,
+                        null);
+                break;
+            case ServiceState.STATE_EMERGENCY_ONLY:
+                respondWithError(
+                        request,
+                        response,
+                        DisconnectCause.EMERGENCY_ONLY,
+                        null);
+                break;
+            case ServiceState.STATE_POWER_OFF:
+                respondWithError(
+                        request,
+                        response,
+                        DisconnectCause.POWER_OFF,
+                        null);
+                break;
+            default:
+                // Internal error, but we pass it upwards and do not crash.
+                Log.d(this, "Unrecognized service state %d", state);
+                respondWithError(
+                        request,
+                        response,
+                        DisconnectCause.ERROR_UNSPECIFIED,
+                        "Unrecognized service state " + state);
+                break;
+        }
+        return false;
+    }
+
     protected <REQUEST, RESULT> void respondWithError(
             REQUEST request,
             Response<REQUEST, RESULT> response,
-            String reason) {
-        Log.d(this, "respondWithError %s: %s", request, reason);
-        response.onError(request, reason);
+            int errorCode,
+            String errorMsg) {
+        Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
+        response.onError(request, errorCode, errorMsg);
     }
 
     protected void respondWithResult(