Merge "Add support for rejecting Telecom call with a specified reason."
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index dd000ce..f5f5c66 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -540,7 +540,7 @@
         if (mConferenceHost == null) {
             return;
         }
-        mConferenceHost.performReject();
+        mConferenceHost.performReject(android.telecom.Call.REJECT_REASON_DECLINED);
     }
 
     /**
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index f2b2244..2f5e2a6 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -113,6 +113,7 @@
     private static final int MSG_SET_CALL_RADIO_TECH = 18;
     private static final int MSG_ON_CONNECTION_EVENT = 19;
     private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
+    private static final int MSG_REJECT = 21;
 
     private List<Uri> mParticipants;
     private boolean mIsAdhocConferenceCall;
@@ -273,6 +274,10 @@
                     int cause = (int) msg.obj;
                     hangup(cause);
                     break;
+                case MSG_REJECT:
+                    int rejectReason = (int) msg.obj;
+                    reject(rejectReason);
+                    break;
 
                 case MSG_SET_CALL_RADIO_TECH:
                     int vrat = (int) msg.obj;
@@ -926,13 +931,18 @@
 
     @Override
     public void onReject() {
-        performReject();
+        performReject(android.telecom.Call.REJECT_REASON_DECLINED);
     }
 
-    public void performReject() {
+    @Override
+    public void onReject(@android.telecom.Call.RejectReason int rejectReason) {
+        performReject(rejectReason);
+    }
+
+    public void performReject(int rejectReason) {
         Log.v(this, "performReject");
         if (isValidRingingCall()) {
-            mHandler.obtainMessage(MSG_HANGUP, android.telephony.DisconnectCause.INCOMING_REJECTED)
+            mHandler.obtainMessage(MSG_REJECT, rejectReason)
                     .sendToTarget();
         }
         super.onReject();
@@ -1678,6 +1688,46 @@
         }
     }
 
+    protected void reject(@android.telecom.Call.RejectReason int rejectReason) {
+        if (mOriginalConnection != null) {
+            mHangupDisconnectCause = android.telephony.DisconnectCause.INCOMING_REJECTED;
+            try {
+                // Hanging up a ringing call requires that we invoke call.hangup() as opposed to
+                // connection.hangup(). Without this change, the party originating the call
+                // will not get sent to voicemail if the user opts to reject the call.
+                if (isValidRingingCall()) {
+                    Call call = getCall();
+                    if (call != null) {
+                        call.hangup(rejectReason);
+                    } else {
+                        Log.w(this, "Attempting to hangup a connection without backing call.");
+                    }
+                } else {
+                    // We still prefer to call connection.hangup() for non-ringing calls
+                    // in order to support hanging-up specific calls within a conference call.
+                    // If we invoked call.hangup() while in a conference, we would end up
+                    // hanging up the entire conference call instead of the specific connection.
+                    mOriginalConnection.hangup();
+                }
+            } catch (CallStateException e) {
+                Log.e(this, e, "Call to Connection.hangup failed with exception");
+            }
+        } else {
+            if (getState() == STATE_DISCONNECTED) {
+                Log.i(this, "hangup called on an already disconnected call!");
+                close();
+            } else {
+                // There are a few cases where mOriginalConnection has not been set yet. For
+                // example, when the radio has to be turned on to make an emergency call,
+                // mOriginalConnection could not be set for many seconds.
+                setTelephonyConnectionDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
+                        android.telephony.DisconnectCause.LOCAL,
+                        "Local Disconnect before connection established."));
+                close();
+            }
+        }
+    }
+
     com.android.internal.telephony.Connection getOriginalConnection() {
         return mOriginalConnection;
     }