Handle disconnected participants which remain in CEP data.
Some carriers will report a disconnected participant in the CEP but then
never remove it from a subsequent CEP update.
This can render the "single participant emulation" inactive as the device
never thinks there is just a single participant in the conference.
This change updates the conference event package handling to check for
disconnected participants and ensure they're cleaned up appropriately.
Test: Add new unit tests for the bug scenario; verify they pass.
Bug: 111860217
Change-Id: I3fd8271368b0023badcfaec3f25564971fed5d06
Merged-In: I3fd8271368b0023badcfaec3f25564971fed5d06
(cherry picked from commit acd96bac6c10e190d95981dd582bb3e47dda4e43)
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 509b2a8..bf0374f 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -750,6 +750,10 @@
// Determine if the conference event package represents a single party conference.
// A single party conference is one where there is no other participant other than the
// conference host and one other participant.
+ // We purposely exclude participants which have a disconnected state in the conference
+ // event package; some carriers are known to keep a disconnected participant around in
+ // subsequent CEP updates with a state of disconnected, even though its no longer part
+ // of the conference.
// Note: We consider 0 to still be a single party conference since some carriers will
// send a conference event package with JUST the host in it when the conference is
// disconnected. We don't want to change back to conference mode prior to disconnection
@@ -757,7 +761,8 @@
boolean isSinglePartyConference = participants.stream()
.filter(p -> {
Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint());
- return !Objects.equals(mHostParticipantIdentity, pIdent);
+ return !Objects.equals(mHostParticipantIdentity, pIdent)
+ && p.getState() != Connection.STATE_DISCONNECTED;
})
.count() <= 1;
@@ -772,7 +777,14 @@
Pair<Uri, Uri> userEntity = new Pair<>(participant.getHandle(),
participant.getEndpoint());
- participantUserEntities.add(userEntity);
+ // We will exclude disconnected participants from the hash set of tracked
+ // participants. Some carriers are known to leave disconnected participants in
+ // the conference event package data which would cause them to be present in the
+ // conference even though they're disconnected. Removing them from the hash set
+ // here means we'll clean them up below.
+ if (participant.getState() != Connection.STATE_DISCONNECTED) {
+ participantUserEntities.add(userEntity);
+ }
if (!mConferenceParticipantConnections.containsKey(userEntity)) {
// Some carriers will also include the conference host in the CEP. We will
// filter that out here.
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
index 68b5b3b..eaec5b6 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -119,6 +119,189 @@
}
/**
+ * Tests CEPs with disconnected participants present with disconnected state.
+ */
+ @Test
+ @SmallTest
+ public void testDisconnectParticipantViaDisconnectState() {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+ // Start off with 3 participants.
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+
+ ConferenceParticipant participant3 = new ConferenceParticipant(
+ Uri.parse("tel:6505551214"),
+ "A",
+ Uri.parse("sip:6505551214@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2, participant3));
+ assertEquals(3, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(3)).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ eq(imsConference));
+
+
+ // Mark one participant as disconnected.
+ ConferenceParticipant participant3Disconnected = new ConferenceParticipant(
+ Uri.parse("tel:6505551214"),
+ "A",
+ Uri.parse("sip:6505551214@testims.com"),
+ Connection.STATE_DISCONNECTED,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2, participant3Disconnected));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(1)).removeConnection(
+ any(Connection.class));
+ reset(mMockTelephonyConnectionServiceProxy);
+
+ // Now remove it from another CEP update; should still be the same number of participants
+ // and no updates.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, never()).removeConnection(
+ any(Connection.class));
+ verify(mMockTelephonyConnectionServiceProxy, never()).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ any(Conference.class));
+ }
+
+ /**
+ * Tests CEPs with removed participants.
+ */
+ @Test
+ @SmallTest
+ public void testDisconnectParticipantViaRemoval() {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+ // Start off with 3 participants.
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+
+ ConferenceParticipant participant3 = new ConferenceParticipant(
+ Uri.parse("tel:6505551214"),
+ "A",
+ Uri.parse("sip:6505551214@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2, participant3));
+ assertEquals(3, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(3)).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ eq(imsConference));
+ reset(mMockTelephonyConnectionServiceProxy);
+
+ // Remove one from the CEP (don't disconnect first); should have 2 participants now.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(1)).removeConnection(
+ any(Connection.class));
+ verify(mMockTelephonyConnectionServiceProxy, never()).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ any(Conference.class));
+ }
+
+ /**
+ * Typically when a participant disconnects from a conference it is either:
+ * 1. Removed from a subsequent CEP update.
+ * 2. Marked as disconnected in a CEP update, and then removed from another CEP update.
+ *
+ * When a participant disconnects from a conference, some carriers will mark the disconnected
+ * participant as disconnected, but fail to send another CEP update with it removed.
+ *
+ * This test verifies that we can still enter single party emulation in this case.
+ */
+ @Test
+ @SmallTest
+ public void testSinglePartyEmulationEnterOnDisconnectParticipant() {
+ when(mMockTelecomAccountRegistry.isUsingSimCallManager(any(PhoneAccountHandle.class)))
+ .thenReturn(false);
+
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+ // Setup the initial conference state with 2 participants.
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(2)).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ eq(imsConference));
+
+ // Some carriers keep disconnected participants around in the CEP; this will cause problems
+ // when we want to enter single party conference mode. Verify that this case is handled.
+ ConferenceParticipant participant2Disconnected = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_DISCONNECTED,
+ Call.Details.DIRECTION_INCOMING);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2Disconnected));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(2)).removeConnection(
+ any(Connection.class));
+ reset(mMockTelephonyConnectionServiceProxy);
+
+ // Pretend to merge someone else into the conference.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ verify(mMockTelephonyConnectionServiceProxy, times(2)).addExistingConnection(
+ any(PhoneAccountHandle.class), any(Connection.class),
+ eq(imsConference));
+ }
+
+ /**
* We have seen a scenario on a carrier where a conference event package comes in just prior to
* the call disconnecting with only the conference host in it. This caused a problem because
* it triggered exiting single party conference mode (due to a bug) and caused the call to not