Conference event package performance improvement.

- Instead of sending each participant to the telephony conference
controller, all participants are sent at once.  This way the conference
only needs to be recalculated once.
- New connections are created with state NEW so that they are not shown
in the InCall UI.
- Once the conference is established, the state of the connections is
set appropriately based on the information in the conference event
package.

Bug: 18057361
Change-Id: Ibdb5cb940f15ab6d872bf1ef8c99a1a9ebae7b6e
diff --git a/src/com/android/services/telephony/TelephonyConference.java b/src/com/android/services/telephony/TelephonyConference.java
index 21dac0c..7736dc1 100644
--- a/src/com/android/services/telephony/TelephonyConference.java
+++ b/src/com/android/services/telephony/TelephonyConference.java
@@ -33,6 +33,12 @@
  */
 public class TelephonyConference extends Conference {
 
+    /**
+     * When {@code true}, indicates that conference participant information from an IMS conference
+     * event package has been received.
+     */
+    private boolean mParticipantsReceived = false;
+
     public TelephonyConference(PhoneAccountHandle phoneAccount) {
         super(phoneAccount);
         setCapabilities(
@@ -133,7 +139,10 @@
     public void onConnectionAdded(Connection connection) {
         // If the conference was an IMS connection currently or before, disable MANAGE_CONFERENCE
         // as the default behavior. If there is a conference event package, this may be overridden.
-        if (((TelephonyConnection) connection).wasImsConnection()) {
+        // If a conference event package was received, do not attempt to remove manage conference.
+        if (connection instanceof TelephonyConnection &&
+                ((TelephonyConnection) connection).wasImsConnection() &&
+                !mParticipantsReceived) {
             int capabilities = getCapabilities();
             if (PhoneCapabilities.can(capabilities, PhoneCapabilities.MANAGE_CONFERENCE)) {
                 int newCapabilities =
@@ -191,4 +200,17 @@
         }
         return (TelephonyConnection) connections.get(0);
     }
+
+    /**
+     * Flags the conference to indicate that a conference event package has been received and there
+     * is now participant data present which would permit conference management.
+     */
+    public void setParticipantsReceived() {
+        if (!mParticipantsReceived) {
+            int capabilities = getCapabilities();
+            capabilities |= PhoneCapabilities.MANAGE_CONFERENCE;
+            setCapabilities(capabilities);
+        }
+        mParticipantsReceived = true;
+    }
 }
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 43385fd..f726c55 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -61,23 +61,22 @@
         }
 
         /**
-         * Handles notifications from an connection that a participant in a conference has changed
+         * Handles notifications from an connection that participant(s) in a conference have changed
          * state.
          *
          * @param c The connection.
-         * @param participant The participant information.
+         * @param participants The participant information.
          */
         @Override
-        public void onConferenceParticipantChanged(Connection c,
-                ConferenceParticipant participant) {
+        public void onConferenceParticipantsChanged(Connection c,
+                List<ConferenceParticipant> participants) {
 
             if (c == null) {
                 return;
             }
+            Log.v(this, "onConferenceParticipantsChanged: %d participants", participants.size());
             TelephonyConnection telephonyConnection = (TelephonyConnection) c;
-
-            Log.v(this, "onConferenceParticipantChanged: %s", participant);
-            handleConferenceParticipantUpdate(telephonyConnection, participant);
+            handleConferenceParticipantsUpdate(telephonyConnection, participants);
         }
     };
 
@@ -219,6 +218,8 @@
         Log.d(this, "Recalculate conference calls %s %s.",
                 mTelephonyConference, conferencedConnections);
 
+        boolean wasParticipantsAdded = false;
+
         // 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.
@@ -240,7 +241,8 @@
                 List<Connection> existingConnections = mTelephonyConference.getConnections();
                 // Remove any that no longer exist
                 for (Connection connection : existingConnections) {
-                    if (!conferencedConnections.contains(connection)) {
+                    if (connection instanceof TelephonyConnection &&
+                            !conferencedConnections.contains(connection)) {
                         mTelephonyConference.removeConnection(connection);
                     }
                 }
@@ -256,8 +258,9 @@
                 for (Connection conferenceParticipant :
                         mConferenceParticipantConnections.values()) {
 
-                    if (conferenceParticipant.getState() == Connection.STATE_ACTIVE) {
+                    if (conferenceParticipant.getState() == Connection.STATE_NEW) {
                         if (!existingConnections.contains(conferenceParticipant)) {
+                            wasParticipantsAdded = true;
                             mTelephonyConference.addConnection(conferenceParticipant);
                         }
                     }
@@ -273,12 +276,19 @@
                 // Add the conference participants
                 for (Connection conferenceParticipant :
                         mConferenceParticipantConnections.values()) {
+                    wasParticipantsAdded = true;
                     mTelephonyConference.addConnection(conferenceParticipant);
                 }
 
                 mConnectionService.addConference(mTelephonyConference);
             }
 
+            // If we added conference participants (e.g. via an IMS conference event package),
+            // notify the conference so that the MANAGE_CONFERENCE capability can be added.
+            if (wasParticipantsAdded) {
+                mTelephonyConference.setParticipantsReceived();
+            }
+
             // Set the conference state to the same state as its child connections.
             Connection conferencedConnection = mTelephonyConference.getPrimaryConnection();
             switch (conferencedConnection.getState()) {
@@ -310,21 +320,40 @@
     }
 
     /**
-     * Handles state changes for a conference participant.
+     * Handles state changes for conference participant(s).
      *
      * @param parent The connection which was notified of the conference participant.
-     * @param participant The conference participant.
+     * @param participants The conference participant information.
      */
-    private void handleConferenceParticipantUpdate(
-            TelephonyConnection parent, ConferenceParticipant participant) {
+    private void handleConferenceParticipantsUpdate(
+            TelephonyConnection parent, List<ConferenceParticipant> participants) {
 
-        Uri endpoint = participant.getEndpoint();
-        if (!mConferenceParticipantConnections.containsKey(endpoint)) {
-            createConferenceParticipantConnection(parent, participant);
-        } else {
-            ConferenceParticipantConnection connection =
-                    mConferenceParticipantConnections.get(endpoint);
-            connection.updateState(participant.getState());
+        boolean recalculateConference = false;
+        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
+
+        for (ConferenceParticipant participant : participants) {
+            Uri endpoint = participant.getEndpoint();
+            if (!mConferenceParticipantConnections.containsKey(endpoint)) {
+                createConferenceParticipantConnection(parent, participant);
+                newParticipants.add(participant);
+                recalculateConference = true;
+            } else {
+                ConferenceParticipantConnection connection =
+                        mConferenceParticipantConnections.get(endpoint);
+                connection.updateState(participant.getState());
+            }
+        }
+
+        if (recalculateConference) {
+            // Recalculate to add new connections to the conference.
+            recalculateConference();
+
+            // Now that conference is established, set the state for all participants.
+            for (ConferenceParticipant newParticipant : newParticipants) {
+                ConferenceParticipantConnection connection =
+                        mConferenceParticipantConnections.get(newParticipant.getEndpoint());
+                connection.updateState(newParticipant.getState());
+            }
         }
     }
 
@@ -346,13 +375,9 @@
         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
                 parent, participant);
         connection.addConnectionListener(mConnectionListener);
-        connection.updateState(Connection.STATE_HOLDING);
         mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
         PhoneAccountHandle phoneAccountHandle =
-                TelecomAccountRegistry.makePstnPhoneAccountHandle(parent.getPhone());
+                 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 da3de63..30312bb 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -35,6 +35,7 @@
 
 import java.lang.Override;
 import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -155,14 +156,14 @@
         }
 
         /**
-         * Handles a change in the state of a conference participant, as reported by the
+         * Handles a change in the state of conference participant(s), as reported by the
          * {@link com.android.internal.telephony.Connection}.
          *
-         * @param participant The participant which changed.
+         * @param participants The participant(s) which changed.
          */
         @Override
-        public void onConferenceParticipantChanged(ConferenceParticipant participant) {
-            updateConferenceParticipant(participant);
+        public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
+            updateConferenceParticipants(participants);
         }
     };
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index c402409..8ef9ae8 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -429,8 +429,10 @@
     @Override
     public void removeConnection(Connection connection) {
         super.removeConnection(connection);
-        TelephonyConnection telephonyConnection = (TelephonyConnection)connection;
-        telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
+        if (connection instanceof TelephonyConnection) {
+            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
+            telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
+        }
     }
 
     /**