Merge "Prevent network depersonalization dialog from showing multiple instances." into mnc-dr-dev
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index c142cc3..a2e3359 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -22,6 +22,7 @@
 import android.telecom.Connection;
 import android.telecom.ConferenceParticipant;
 import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
 
 /**
  * Represents a participant in a conference call.
@@ -53,7 +54,7 @@
             ConferenceParticipant participant) {
 
         mParentConnection = parentConnection;
-        setAddress(participant.getHandle(), PhoneConstants.PRESENTATION_ALLOWED);
+        setAddress(getParticipantAddress(participant), PhoneConstants.PRESENTATION_ALLOWED);
         setCallerDisplayName(participant.getDisplayName(), PhoneConstants.PRESENTATION_ALLOWED);
 
         mUserEntity = participant.getHandle();
@@ -140,6 +141,45 @@
     }
 
     /**
+     * Attempts to build a tel: style URI from a conference participant.
+     * Conference event package data contains SIP URIs, so we try to extract the phone number and
+     * format into a typical tel: style URI.
+     *
+     * @param participant The conference participant.
+     * @return The participant's address URI.
+     */
+    private Uri getParticipantAddress(ConferenceParticipant participant) {
+        Uri address = participant.getHandle();
+
+        // If the participant's address is already a TEL scheme, just return it as is.
+        if (address.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
+            return address;
+        }
+
+        // Conference event package participants are identified using SIP URIs (see RFC3261).
+        // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers
+        // Per RFC3261, the "user" can be a telephone number.
+        // For example: sip:1650555121;phone-context=blah.com@host.com
+        // In this case, the phone number is in the user field of the URI, and the parameters can be
+        // ignored.
+        //
+        // A SIP URI can also specify a phone number in a format similar to:
+        // sip:+1-212-555-1212@something.com;user=phone
+        // In this case, the phone number is again in user field and the parameters can be ignored.
+        // We can get the user field in these instances by splitting the string on the @, ;, or :
+        // and looking at the first found item.
+        String number = address.getSchemeSpecificPart();
+        String numberParts[] = number.split("[@;:]");
+
+        if (numberParts.length == 0) {
+            return address;
+        }
+        number = numberParts[0];
+
+        return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
+    }
+
+    /**
      * Builds a string representation of this conference participant connection.
      *
      * @return String representation of connection.
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index c4ae4a1..4f26a68 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -39,12 +39,12 @@
 import com.android.phone.R;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Represents an IMS conference call.
@@ -175,7 +175,7 @@
             Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
                     connectionCapabilities);
             int capabilites = ImsConference.this.getConnectionCapabilities();
-            setConnectionCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+            setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities));
         }
 
         @Override
@@ -207,12 +207,19 @@
 
     /**
      * The known conference participant connections.  The HashMap is keyed by endpoint Uri.
-     * A {@link ConcurrentHashMap} is used as there is a possibility for radio events impacting the
-     * available participants to occur at the same time as an access via the connection service.
+     * Access to the hashmap is protected by the {@link #mUpdateSyncRoot}.
      */
-    private final ConcurrentHashMap<Uri, ConferenceParticipantConnection>
-            mConferenceParticipantConnections =
-                    new ConcurrentHashMap<Uri, ConferenceParticipantConnection>(8, 0.9f, 1);
+    private final HashMap<Uri, ConferenceParticipantConnection>
+            mConferenceParticipantConnections = new HashMap<Uri, ConferenceParticipantConnection>();
+
+    /**
+     * Sychronization root used to ensure that updates to the
+     * {@link #mConferenceParticipantConnections} happen atomically are are not interleaved across
+     * threads.  There are some instances where the network will send conference event package
+     * data closely spaced.  If that happens, it is possible that the interleaving of the update
+     * will cause duplicate participant info to be added.
+     */
+    private final Object mUpdateSyncRoot = new Object();
 
     public void updateConferenceParticipantsAfterCreation() {
         if (mConferenceHost != null) {
@@ -251,13 +258,20 @@
 
         int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
                 Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
-
-        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getConnectionCapabilities());
+        capabilities = applyHostCapabilities(capabilities,
+                mConferenceHost.getConnectionCapabilities());
         setConnectionCapabilities(capabilities);
 
     }
 
-    private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
+    /**
+     * Transfers capabilities from the conference host to the conference itself.
+     *
+     * @param conferenceCapabilities The current conference capabilities.
+     * @param capabilities The new conference host capabilities.
+     * @return The merged capabilities to be applied to the conference.
+     */
+    private int applyHostCapabilities(int conferenceCapabilities, int capabilities) {
         if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
             conferenceCapabilities = applyCapability(conferenceCapabilities,
                     Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
@@ -281,6 +295,14 @@
             conferenceCapabilities = removeCapability(conferenceCapabilities,
                     Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
         }
+
+        if (can(capabilities, Connection.CAPABILITY_HIGH_DEF_AUDIO)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_HIGH_DEF_AUDIO);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_HIGH_DEF_AUDIO);
+        }
         return conferenceCapabilities;
     }
 
@@ -536,63 +558,70 @@
         if (participants == null) {
             return;
         }
-        boolean newParticipantsAdded = false;
-        boolean oldParticipantsRemoved = false;
-        ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
-        HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
 
-        // Add any new participants and update existing.
-        for (ConferenceParticipant participant : participants) {
-            Uri userEntity = participant.getHandle();
+        // Perform the update in a synchronized manner.  It is possible for the IMS framework to
+        // trigger two onConferenceParticipantsChanged callbacks in quick succession.  If the first
+        // update adds new participants, and the second does something like update the status of one
+        // of the participants, we can get into a situation where the participant is added twice.
+        synchronized (mUpdateSyncRoot) {
+            boolean newParticipantsAdded = false;
+            boolean oldParticipantsRemoved = false;
+            ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
+            HashSet<Uri> participantUserEntities = new HashSet<>(participants.size());
 
-            participantUserEntities.add(userEntity);
-            if (!mConferenceParticipantConnections.containsKey(userEntity)) {
-                // Some carriers will also include the conference host in the CEP.  We will filter
-                // that out here.
-                if (!isParticipantHost(mConferenceHostAddress, participant.getHandle())) {
-                    createConferenceParticipantConnection(parent, participant);
-                    newParticipants.add(participant);
-                    newParticipantsAdded = true;
+            // Add any new participants and update existing.
+            for (ConferenceParticipant participant : participants) {
+                Uri userEntity = participant.getHandle();
+
+                participantUserEntities.add(userEntity);
+                if (!mConferenceParticipantConnections.containsKey(userEntity)) {
+                    // Some carriers will also include the conference host in the CEP.  We will
+                    // filter that out here.
+                    if (!isParticipantHost(mConferenceHostAddress, userEntity)) {
+                        createConferenceParticipantConnection(parent, participant);
+                        newParticipants.add(participant);
+                        newParticipantsAdded = true;
+                    }
+                } else {
+                    ConferenceParticipantConnection connection =
+                            mConferenceParticipantConnections.get(userEntity);
+                    connection.updateState(participant.getState());
                 }
-            } else {
-                ConferenceParticipantConnection connection =
-                        mConferenceParticipantConnections.get(userEntity);
-                connection.updateState(participant.getState());
             }
-        }
 
-        // Set state of new participants.
-        if (newParticipantsAdded) {
-            // Set the state of the new participants at once and add to the conference
-            for (ConferenceParticipant newParticipant : newParticipants) {
-                ConferenceParticipantConnection connection =
-                        mConferenceParticipantConnections.get(newParticipant.getHandle());
-                connection.updateState(newParticipant.getState());
+            // Set state of new participants.
+            if (newParticipantsAdded) {
+                // Set the state of the new participants at once and add to the conference
+                for (ConferenceParticipant newParticipant : newParticipants) {
+                    ConferenceParticipantConnection connection =
+                            mConferenceParticipantConnections.get(newParticipant.getHandle());
+                    connection.updateState(newParticipant.getState());
+                }
             }
-        }
 
-        // Finally, remove any participants from the conference that no longer exist in the
-        // conference event package data.
-        Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
-                mConferenceParticipantConnections.entrySet().iterator();
-        while (entryIterator.hasNext()) {
-            Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
+            // Finally, remove any participants from the conference that no longer exist in the
+            // conference event package data.
+            Iterator<Map.Entry<Uri, ConferenceParticipantConnection>> entryIterator =
+                    mConferenceParticipantConnections.entrySet().iterator();
+            while (entryIterator.hasNext()) {
+                Map.Entry<Uri, ConferenceParticipantConnection> entry = entryIterator.next();
 
-            if (!participantUserEntities.contains(entry.getKey())) {
-                ConferenceParticipantConnection participant = entry.getValue();
-                participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
-                participant.removeConnectionListener(mParticipantListener);
-                mTelephonyConnectionService.removeConnection(participant);
-                removeConnection(participant);
-                entryIterator.remove();
-                oldParticipantsRemoved = true;
+                if (!participantUserEntities.contains(entry.getKey())) {
+                    ConferenceParticipantConnection participant = entry.getValue();
+                    participant.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+                    participant.removeConnectionListener(mParticipantListener);
+                    mTelephonyConnectionService.removeConnection(participant);
+                    removeConnection(participant);
+                    entryIterator.remove();
+                    oldParticipantsRemoved = true;
+                }
             }
-        }
 
-        // If new participants were added or old ones were removed, we need to ensure the state of
-        // the manage conference capability is updated.
-        if (newParticipantsAdded || oldParticipantsRemoved) {
-            updateManageConference();
+            // If new participants were added or old ones were removed, we need to ensure the state
+            // of the manage conference capability is updated.
+            if (newParticipantsAdded || oldParticipantsRemoved) {
+                updateManageConference();
+            }
         }
     }
 
@@ -620,7 +649,9 @@
             Log.v(this, "createConferenceParticipantConnection: %s", connection);
         }
 
-        mConferenceParticipantConnections.put(participant.getHandle(), connection);
+        synchronized(mUpdateSyncRoot) {
+            mConferenceParticipantConnections.put(participant.getHandle(), connection);
+        }
         mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
                 connection);
         addConnection(connection);
@@ -635,7 +666,9 @@
         Log.d(this, "removeConferenceParticipant: %s", participant);
 
         participant.removeConnectionListener(mParticipantListener);
-        mConferenceParticipantConnections.remove(participant.getUserEntity());
+        synchronized(mUpdateSyncRoot) {
+            mConferenceParticipantConnections.remove(participant.getUserEntity());
+        }
         mTelephonyConnectionService.removeConnection(participant);
     }
 
@@ -645,17 +678,19 @@
     private void disconnectConferenceParticipants() {
         Log.v(this, "disconnectConferenceParticipants");
 
-        for (ConferenceParticipantConnection connection :
-                mConferenceParticipantConnections.values()) {
+        synchronized(mUpdateSyncRoot) {
+            for (ConferenceParticipantConnection connection :
+                    mConferenceParticipantConnections.values()) {
 
-            connection.removeConnectionListener(mParticipantListener);
-            // Mark disconnect cause as cancelled to ensure that the call is not logged in the
-            // call log.
-            connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
-            mTelephonyConnectionService.removeConnection(connection);
-            connection.destroy();
+                connection.removeConnectionListener(mParticipantListener);
+                // Mark disconnect cause as cancelled to ensure that the call is not logged in the
+                // call log.
+                connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
+                mTelephonyConnectionService.removeConnection(connection);
+                connection.destroy();
+            }
+            mConferenceParticipantConnections.clear();
         }
-        mConferenceParticipantConnections.clear();
     }
 
     /**