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();
}
/**