Merge "Remove unneeded abstraction in displaying dialogs." into lmp-mr1-dev
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index b3b1d80..09083ac 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -292,7 +292,7 @@
 
     private void initAccountList() {
         for (SubInfoRecord subscription : SubscriptionManager.getActiveSubInfoList()) {
-            String label = subscription.getDisplayName().toString();
+            CharSequence label = subscription.getDisplayName();
             Intent intent = new Intent(TelecomManager.ACTION_SHOW_CALL_SETTINGS);
             intent.putExtra(CallFeaturesSetting.SUB_ID_EXTRA, subscription.getSubscriptionId());
             intent.putExtra(CallFeaturesSetting.SUB_LABEL_EXTRA, label);
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index 7874404..e748634 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -61,6 +61,10 @@
      * @param newState The new state.
      */
     public void updateState(int newState) {
+        if (newState == getState()) {
+            return;
+        }
+
         switch (newState) {
             case STATE_INITIALIZING:
                 setInitializing();
@@ -78,7 +82,8 @@
                 setActive();
                 break;
             case STATE_DISCONNECTED:
-                setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+                setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+                destroy();
                 break;
             default:
                 setActive();
@@ -86,6 +91,21 @@
     }
 
     /**
+     * Disconnects the current {@code ConferenceParticipantConnection} from the conference.
+     * <p>
+     * Sends a participant disconnect signal to the associated parent connection.  The participant
+     * connection is not disconnected and cleaned up here.  On successful disconnection of the
+     * participant, the conference server will send an update to the conference controller
+     * indicating the disconnection was successful.
+     */
+    @Override
+    public void onDisconnect() {
+        Log.v(this, "onDisconnect");
+
+        mParentConnection.onDisconnectConferenceParticipant(mEndpoint);
+    }
+
+    /**
      * Configures the {@link android.telecom.PhoneCapabilities} applicable to this connection.  A
      * conference participant can only be disconnected from a conference since there is not
      * actual connection to the participant which could be split from the conference.
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index ad74ca4..49767a2 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -103,7 +103,7 @@
 
             // Populate the phone account data.
             int subId = mPhone.getSubId();
-            int color = 0;
+            int color = PhoneAccount.NO_COLOR;
             int slotId = SubscriptionManager.INVALID_SLOT_ID;
             String line1Number = mTelephonyManager.getLine1NumberForSubscriber(subId);
             if (line1Number == null) {
@@ -126,12 +126,12 @@
                 // the network is.
                 description = label = mTelephonyManager.getNetworkOperatorName();
             } else {
-                String subDisplayName = null;
+                CharSequence subDisplayName = null;
                 // We can only get the real slotId from the SubInfoRecord, we can't calculate the
                 // slotId from the subId or the phoneId in all instances.
                 SubInfoRecord record = SubscriptionManager.getSubInfoForSubscriber(subId);
                 if (record != null) {
-                    subDisplayName = record.getDisplayName().toString();
+                    subDisplayName = record.getDisplayName();
                     slotId = record.getSubscriptionId();
 
                     // Assign a "fake" color while the underlying Telephony stuff is refactored.
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 892f3f9..43385fd 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -31,6 +31,8 @@
 import android.telecom.PhoneAccountHandle;
 
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.gsm.GsmConnection;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 
 /**
  * Maintains a list of all the known TelephonyConnections connections and controls GSM and
@@ -55,7 +57,7 @@
 
         @Override
         public void onDestroyed(Connection connection) {
-            remove((TelephonyConnection) connection);
+            remove(connection);
         }
 
         /**
@@ -103,9 +105,15 @@
         recalculate();
     }
 
-    void remove(TelephonyConnection connection) {
+    void remove(Connection connection) {
         connection.removeConnectionListener(mConnectionListener);
-        mTelephonyConnections.remove(connection);
+
+        if (connection instanceof ConferenceParticipantConnection) {
+            mConferenceParticipantConnections.remove(connection);
+        } else {
+            mTelephonyConnections.remove(connection);
+        }
+
         recalculate();
     }
 
@@ -185,6 +193,9 @@
     private void recalculateConference() {
         Set<Connection> conferencedConnections = new HashSet<>();
 
+        int numGsmConnections = 0;
+        int numImsConnections = 0;
+
         for (TelephonyConnection connection : mTelephonyConnections) {
             com.android.internal.telephony.Connection radioConnection =
                 connection.getOriginalConnection();
@@ -194,22 +205,30 @@
                 Call call = radioConnection.getCall();
                 if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
                         (call != null && call.isMultiparty())) {
+
+                    if (radioConnection instanceof GsmConnection) {
+                        numGsmConnections++;
+                    } else if (radioConnection instanceof ImsPhoneConnection) {
+                        numImsConnections++;
+                    }
                     conferencedConnections.add(connection);
                 }
             }
         }
 
-        // Include conference participants in the list of conferenced connections.
-        for (ConferenceParticipantConnection participant :
-                mConferenceParticipantConnections.values()) {
-            conferencedConnections.add(participant);
-        }
-
         Log.d(this, "Recalculate conference calls %s %s.",
                 mTelephonyConference, conferencedConnections);
 
-        if (conferencedConnections.size() < 2) {
-            Log.d(this, "less than two conference calls!");
+        // If the number of telephony connections drops below the limit, the conference can be
+        // considered terminated.
+        // We must have less than 2 GSM connections and less than 1 IMS connection.
+        if (numGsmConnections < 2 && numImsConnections < 1) {
+            Log.d(this, "not enough connections to be a conference!");
+
+            // The underlying telephony connections have been disconnected -- disconnect the
+            // conference participants now.
+            disconnectConferenceParticipants();
+
             // No more connections are conferenced, destroy any existing conference.
             if (mTelephonyConference != null) {
                 Log.d(this, "with a conference to destroy!");
@@ -232,6 +251,17 @@
                         mTelephonyConference.addConnection(connection);
                     }
                 }
+
+                // Add new conference participants
+                for (Connection conferenceParticipant :
+                        mConferenceParticipantConnections.values()) {
+
+                    if (conferenceParticipant.getState() == Connection.STATE_ACTIVE) {
+                        if (!existingConnections.contains(conferenceParticipant)) {
+                            mTelephonyConference.addConnection(conferenceParticipant);
+                        }
+                    }
+                }
             } else {
                 mTelephonyConference = new TelephonyConference(null);
                 for (Connection connection : conferencedConnections) {
@@ -239,6 +269,13 @@
                             mTelephonyConference, connection);
                     mTelephonyConference.addConnection(connection);
                 }
+
+                // Add the conference participants
+                for (Connection conferenceParticipant :
+                        mConferenceParticipantConnections.values()) {
+                    mTelephonyConference.addConnection(conferenceParticipant);
+                }
+
                 mConnectionService.addConference(mTelephonyConference);
             }
 
@@ -256,6 +293,23 @@
     }
 
     /**
+     * Disconnects all conference participants from the conference.
+     */
+    private void disconnectConferenceParticipants() {
+        for (Connection connection : mConferenceParticipantConnections.values()) {
+            // Disconnect listener so that the connection doesn't fire events on the conference
+            // controller, causing a recursive call.
+            connection.removeConnectionListener(mConnectionListener);
+            mConferenceParticipantConnections.remove(connection);
+
+            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
+            // call log.
+            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+            connection.destroy();
+        }
+    }
+
+    /**
      * Handles state changes for a conference participant.
      *
      * @param parent The connection which was notified of the conference participant.
@@ -297,8 +351,8 @@
         PhoneAccountHandle phoneAccountHandle =
                 TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
         mConnectionService.addExistingConnection(phoneAccountHandle, connection);
-
         // Recalculate to add to the conference and set its state appropriately.
         recalculateConference();
+        connection.updateState(participant.getState());
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 3af7481..e478d11 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -211,6 +211,23 @@
         hangup(android.telephony.DisconnectCause.LOCAL);
     }
 
+    /**
+     * Notifies this Connection of a request to disconnect a participant of the conference managed
+     * by the connection.
+     *
+     * @param endpoint the {@link Uri} of the participant to disconnect.
+     */
+    @Override
+    public void onDisconnectConferenceParticipant(Uri endpoint) {
+        Log.v(this, "onDisconnectConferenceParticipant %s", endpoint);
+
+        if (mOriginalConnection == null) {
+            return;
+        }
+
+        mOriginalConnection.onDisconnectConferenceParticipant(endpoint);
+    }
+
     @Override
     public void onSeparate() {
         Log.v(this, "onSeparate");