IMS SRVCC handling and call log cleanup.

1. Added TelephonyConnectionListener instance to ImsConference to capture
onOriginalConnectionConfigured events.  When SRVCC happens, the original
connection on the conference host TelephonyConnection is changed to a
GsmConnection; this triggers the onOriginalConnectionConfigured callback.
In response, we add the conference host connection to Telecom (as it is
not already in telecom) and unlink it from the ImsConference.  At this
point the ImsConference is disconnected, leaving just the
TelephonyConnection with the new GsmConnection active.
2. Since in (1) we are unlinking the mConferenceHost, added null checks to
ensure that any other calls to conference methods during SRVCC don't cause
a NPE.
3. In ImsConferenceController when the new conference is being created,
we disconnect the original foreground TelephonyConnection and move its
radio connection to be a new TelephonyConnection which is the ImsConference
host.  When the original foreground connection is disconnected, changed
the disconnect cause to "OTHER" to ensure that the call is logged to the
call log, and no disconnect tone is heard.

Bug: 18433280
Bug: 18323703
Change-Id: I77a57d895e43c81e14b012409e2de0be7eeb48db
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 0d235c3..5fc3e41 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -19,6 +19,7 @@
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 
 import android.net.Uri;
 import android.telecom.Connection;
@@ -84,6 +85,21 @@
     };
 
     /**
+     * Listener used to respond to changes to the underlying radio connection for the conference
+     * host connection.  Used to respond to SRVCC changes.
+     */
+    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
+            new TelephonyConnection.TelephonyConnectionListener() {
+
+        @Override
+        public void onOriginalConnectionConfigured(TelephonyConnection c) {
+            if (c == mConferenceHost) {
+               handleOriginalConnectionChange();
+            }
+        }
+    };
+
+    /**
      * Listener used to respond to changes to the connection to the IMS conference server.
      */
     private final android.telecom.Connection.Listener mConferenceHostListener =
@@ -202,6 +218,9 @@
     @Override
     public void onDisconnect() {
         Log.v(this, "onDisconnect: hanging up conference host.");
+        if (mConferenceHost == null) {
+            return;
+        }
 
         Call call = mConferenceHost.getCall();
         if (call != null) {
@@ -249,6 +268,9 @@
      */
     @Override
     public void onHold() {
+        if (mConferenceHost == null) {
+            return;
+        }
         mConferenceHost.performHold();
     }
 
@@ -257,6 +279,9 @@
      */
     @Override
     public void onUnhold() {
+        if (mConferenceHost == null) {
+            return;
+        }
         mConferenceHost.performUnhold();
     }
 
@@ -267,7 +292,10 @@
      */
     @Override
     public void onPlayDtmfTone(char c) {
-         mConferenceHost.onPlayDtmfTone(c);
+        if (mConferenceHost == null) {
+            return;
+        }
+        mConferenceHost.onPlayDtmfTone(c);
     }
 
     /**
@@ -275,6 +303,9 @@
      */
     @Override
     public void onStopDtmfTone() {
+        if (mConferenceHost == null) {
+            return;
+        }
         mConferenceHost.onStopDtmfTone();
     }
 
@@ -326,6 +357,7 @@
 
         mConferenceHost = new ConferenceHostConnection(conferenceHost);
         mConferenceHost.addConnectionListener(mConferenceHostListener);
+        mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
     }
 
     /**
@@ -455,6 +487,41 @@
     }
 
     /**
+     * Handles a change in the original connection backing the conference host connection.  This can
+     * happen if an SRVCC event occurs on the original IMS connection, requiring a fallback to
+     * GSM or CDMA.
+     * <p>
+     * If this happens, we will add the conference host connection to telecom and tear down the
+     * conference.
+     */
+    private void handleOriginalConnectionChange() {
+        if (mConferenceHost == null) {
+            Log.w(this, "handleOriginalConnectionChange; conference host missing.");
+            return;
+        }
+
+        com.android.internal.telephony.Connection originalConnection =
+                mConferenceHost.getOriginalConnection();
+
+        if (!(originalConnection instanceof ImsPhoneConnection)) {
+            if (Log.VERBOSE) {
+                Log.v(this,
+                        "Original connection for conference host is no longer an IMS connection; " +
+                                "new connection: %s", originalConnection);
+            }
+
+            PhoneAccountHandle phoneAccountHandle =
+                    TelecomAccountRegistry.makePstnPhoneAccountHandle(mConferenceHost.getPhone());
+            mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, mConferenceHost);
+            mConferenceHost.removeConnectionListener(mConferenceHostListener);
+            mConferenceHost.removeTelephonyConnectionListener(mTelephonyConnectionListener);
+            mConferenceHost = null;
+            setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
+            destroy();
+        }
+    }
+
+    /**
      * Changes the state of the Ims conference.
      *
      * @param state the new state.
@@ -470,8 +537,14 @@
                 // No-op -- not applicable.
                 break;
             case Connection.STATE_DISCONNECTED:
-                setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                        mConferenceHost.getOriginalConnection().getDisconnectCause()));
+                DisconnectCause disconnectCause;
+                if (mConferenceHost == null) {
+                    disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
+                } else {
+                    disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                            mConferenceHost.getOriginalConnection().getDisconnectCause());
+                }
+                setDisconnected(disconnectCause);
                 destroy();
                 break;
             case Connection.STATE_ACTIVE:
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 6d92fbb..e924687 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -264,10 +264,11 @@
         conference.addListener(mConferenceListener);
 
         // Cleanup TelephonyConnection which backed the original connection and remove from telecom.
-        // Use the "Canceled" disconnect cause to ensure the call is not logged.
+        // Use the "Other" disconnect cause to ensure the call is logged to the call log but the
+        // disconnect tone is not played.
         connection.removeConnectionListener(mConnectionListener);
         connection.clearOriginalConnection();
-        connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+        connection.setDisconnected(new DisconnectCause(DisconnectCause.OTHER));
         connection.destroy();
         mImsConferences.add(conference);
     }