Merge "Key Conference Participants by User and Endpoint Uris" into nyc-mr1-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c003662..61e0a5b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -995,6 +995,8 @@
     <string name="incall_error_no_phone_number_supplied">To place a call, enter a valid number.</string>
     <!-- In-call screen: call failure message displayed in an error dialog -->
     <string name="incall_error_call_failed">Call failed.</string>
+    <!-- In-call screen: call failure message displayed in an error dialog -->
+    <string name="incall_error_cannot_add_call">Call cannot be added at this time.</string>
     <!-- In-call screen: status message displayed in a dialog when starting an MMI -->
     <string name="incall_status_dialed_mmi">Starting MMI sequence\u2026</string>
     <!-- In-call screen: message displayed in an error dialog -->
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index d75481b..d017a9e 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -29,8 +29,10 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Manages conferences for IMS connections.
@@ -169,8 +171,8 @@
      */
     private void recalculateConferenceable() {
         Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
-        List<Conferenceable> activeConnections = new ArrayList<>(mTelephonyConnections.size());
-        List<Conferenceable> backgroundConnections = new ArrayList<>(mTelephonyConnections.size());
+        HashSet<Conferenceable> conferenceableSet = new HashSet<>(mTelephonyConnections.size() +
+                mImsConferences.size());
 
         // Loop through and collect all calls which are active or holding
         for (TelephonyConnection connection : mTelephonyConnections) {
@@ -191,23 +193,23 @@
             // If this connection does not support being in a conference call, then it is not
             // conferenceable with any other connection.
             if (!connection.isConferenceSupported()) {
-                connection.setConferenceableConnections(Collections.<Connection>emptyList());
+                connection.setConferenceables(Collections.<Conferenceable>emptyList());
                 continue;
             }
 
             switch (connection.getState()) {
                 case Connection.STATE_ACTIVE:
-                    activeConnections.add(connection);
-                    continue;
+                    // fall through
                 case Connection.STATE_HOLDING:
-                    backgroundConnections.add(connection);
+                    conferenceableSet.add(connection);
                     continue;
                 default:
                     break;
             }
-            connection.setConferenceableConnections(Collections.<Connection>emptyList());
+            // This connection is not active or holding, so clear all conferencable connections
+            connection.setConferenceables(Collections.<Conferenceable>emptyList());
         }
-
+        // Also loop through all active conferences and collect the ones that are ACTIVE or HOLDING.
         for (ImsConference conference : mImsConferences) {
             if (Log.DEBUG) {
                 Log.d(this, "recalc - %s %s", conference.getState(), conference);
@@ -222,62 +224,37 @@
 
             switch (conference.getState()) {
                 case Connection.STATE_ACTIVE:
-                    activeConnections.add(conference);
-                    continue;
+                    //fall through
                 case Connection.STATE_HOLDING:
-                    backgroundConnections.add(conference);
+                    conferenceableSet.add(conference);
                     continue;
                 default:
                     break;
             }
         }
 
-        Log.v(this, "active: %d, holding: %d", activeConnections.size(),
-                backgroundConnections.size());
+        Log.v(this, "conferenceableSet size: " + conferenceableSet.size());
 
-        // Go through all the active connections and set the background connections as
-        // conferenceable.
-        for (Conferenceable conferenceable : activeConnections) {
-            if (conferenceable instanceof Connection) {
-                Connection connection = (Connection) conferenceable;
-                connection.setConferenceables(backgroundConnections);
+        for (Conferenceable c : conferenceableSet) {
+            if (c instanceof Connection) {
+                // Remove this connection from the Set and add all others
+                List<Conferenceable> conferenceables = conferenceableSet
+                        .stream()
+                        .filter(conferenceable -> c != conferenceable)
+                        .collect(Collectors.toList());
+                ((Connection) c).setConferenceables(conferenceables);
+            } else if (c instanceof Conference) {
+                // Remove all conferences from the set, since we can not conference a conference
+                // to another conference.
+                List<Connection> connections = conferenceableSet
+                        .stream()
+                        .filter(conferenceable -> conferenceable instanceof Connection)
+                        .map(conferenceable -> (Connection) conferenceable)
+                        .collect(Collectors.toList());
+                // Conference equivalent to setConferenceables that only accepts Connections
+                ((Conference) c).setConferenceableConnections(connections);
             }
         }
-
-        // Go through all the background connections and set the active connections as
-        // conferenceable.
-        for (Conferenceable conferenceable : backgroundConnections) {
-            if (conferenceable instanceof Connection) {
-                Connection connection = (Connection) conferenceable;
-                connection.setConferenceables(activeConnections);
-            }
-
-        }
-
-        // Set the conference as conferenceable with all the connections
-        for (ImsConference conference : mImsConferences) {
-            // If this conference is not being hosted on the current device, we cannot conference it
-            // with any other connections.
-            if (!conference.isConferenceHost()) {
-                if (Log.VERBOSE) {
-                    Log.v(this, "skipping conference (not hosted on this device): %s",
-                            conference);
-                }
-                continue;
-            }
-
-            List<Connection> nonConferencedConnections =
-                new ArrayList<>(mTelephonyConnections.size());
-            for (TelephonyConnection c : mTelephonyConnections) {
-                if (c.getConference() == null && c.isConferenceSupported()) {
-                    nonConferencedConnections.add(c);
-                }
-            }
-            if (Log.VERBOSE) {
-                Log.v(this, "conference conferenceable: %s", nonConferencedConnections);
-            }
-            conference.setConferenceableConnections(nonConferencedConnections);
-        }
     }
 
     /**
diff --git a/src/com/android/services/telephony/TelephonyConferenceController.java b/src/com/android/services/telephony/TelephonyConferenceController.java
index 7da9ea5..fbf5ad0 100644
--- a/src/com/android/services/telephony/TelephonyConferenceController.java
+++ b/src/com/android/services/telephony/TelephonyConferenceController.java
@@ -23,6 +23,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import android.net.Uri;
 import android.telecom.Conference;
@@ -113,10 +114,7 @@
      */
     private void recalculateConferenceable() {
         Log.v(this, "recalculateConferenceable : %d", mTelephonyConnections.size());
-
-        List<Connection> activeConnections = new ArrayList<>(mTelephonyConnections.size());
-        List<Connection> backgroundConnections = new ArrayList<>(
-                mTelephonyConnections.size());
+        HashSet<Connection> conferenceableConnections = new HashSet<>(mTelephonyConnections.size());
 
         // Loop through and collect all calls which are active or holding
         for (TelephonyConnection connection : mTelephonyConnections) {
@@ -126,10 +124,9 @@
             if (connection.isConferenceSupported() && !participatesInFullConference(connection)) {
                 switch (connection.getState()) {
                     case Connection.STATE_ACTIVE:
-                        activeConnections.add(connection);
-                        continue;
+                        //fall through
                     case Connection.STATE_HOLDING:
-                        backgroundConnections.add(connection);
+                        conferenceableConnections.add(connection);
                         continue;
                     default:
                         break;
@@ -139,34 +136,30 @@
             connection.setConferenceableConnections(Collections.<Connection>emptyList());
         }
 
-        Log.v(this, "active: %d, holding: %d",
-                activeConnections.size(), backgroundConnections.size());
+        Log.v(this, "conferenceable: " + conferenceableConnections.size());
 
-        // Go through all the active connections and set the background connections as
-        // conferenceable.
-        for (Connection connection : activeConnections) {
-            connection.setConferenceableConnections(backgroundConnections);
+        // Go through all the conferenceable connections and add all other conferenceable
+        // connections that is not the connection itself
+        for (Connection c : conferenceableConnections) {
+            List<Connection> connections = conferenceableConnections
+                    .stream()
+                    // Filter out this connection from the list of connections
+                    .filter(connection -> c != connection)
+                    .collect(Collectors.toList());
+            c.setConferenceableConnections(connections);
         }
 
-        // Go through all the background connections and set the active connections as
-        // conferenceable.
-        for (Connection connection : backgroundConnections) {
-            connection.setConferenceableConnections(activeConnections);
-        }
-
-        // Set the conference as conferenceable with all the connections
+        // Set the conference as conferenceable with all of the connections that are not in the
+        // conference.
         if (mTelephonyConference != null && !isFullConference(mTelephonyConference)) {
-            List<Connection> nonConferencedConnections =
-                    new ArrayList<>(mTelephonyConnections.size());
-            for (TelephonyConnection c : mTelephonyConnections) {
-                if (c.isConferenceSupported() && c.getConference() == null) {
-                    nonConferencedConnections.add(c);
-                }
-            }
-            Log.v(this, "conference conferenceable: %s", nonConferencedConnections);
+            List<Connection> nonConferencedConnections = mTelephonyConnections
+                    .stream()
+                    // Only retrieve Connections that are not in a conference (but support
+                    // conferences).
+                    .filter(c -> c.isConferenceSupported() && c.getConference() == null)
+                    .collect(Collectors.toList());
             mTelephonyConference.setConferenceableConnections(nonConferencedConnections);
         }
-
         // TODO: Do not allow conferencing of already conferenced connections.
     }
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a9f2e14..a8f6bca 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -53,6 +53,7 @@
 import com.android.phone.R;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -242,6 +243,17 @@
             // connecting it to the underlying Phone.
             return emergencyConnection;
         } else {
+            if (!canAddCall() && !isEmergencyNumber) {
+                Log.d(this, "onCreateOutgoingConnection, cannot add call .");
+                return Connection.createFailedConnection(
+                        new DisconnectCause(DisconnectCause.ERROR,
+                                getApplicationContext().getText(
+                                        R.string.incall_error_cannot_add_call),
+                                getApplicationContext().getText(
+                                        R.string.incall_error_cannot_add_call),
+                                "Add call restricted due to ongoing video call"));
+            }
+
             // Get the right phone object from the account data passed in.
             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
             Connection resultConnection = getTelephonyConnection(request, number, isEmergencyNumber,
@@ -255,6 +267,21 @@
         }
     }
 
+    /**
+     * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
+     *      otherwise.
+     */
+    private boolean canAddCall() {
+        Collection<Connection> connections = getAllConnections();
+        for (Connection connection : connections) {
+            if (connection.getExtras() != null &&
+                    connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
             boolean isEmergencyNumber, final Uri handle, Phone phone) {