Treat single party IMS conference as a standalone call.
Where an IMS conference drops to a single participant, remove that child
from the conference and tell telecom to no longer treat the conference as
if it is a conference. Also pass through the participant name/number to
telecom. When recalculating whether manage conference is supported, do
not allow manage conference if there is just a single participant.
Test: manual
Bug: 75975913
Change-Id: Iba9a65bc37df5df6472c51183410b774219c9eb2
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 5722834..d5af25b 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -29,11 +29,14 @@
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
+import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Phone;
@@ -44,11 +47,13 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Represents an IMS conference call.
@@ -68,6 +73,13 @@
public class ImsConference extends Conference implements Holdable {
/**
+ * Abstracts out fetching a feature flag. Makes testing easier.
+ */
+ public interface FeatureFlagProxy {
+ boolean isUsingSinglePartyCallEmulation();
+ }
+
+ /**
* Listener used to respond to changes to conference participants. At the conference level we
* are most concerned with handling destruction of a conference participant.
*/
@@ -241,6 +253,25 @@
private final Object mUpdateSyncRoot = new Object();
private boolean mIsHoldable;
+ private boolean mCouldManageConference;
+ private FeatureFlagProxy mFeatureFlagProxy;
+ private boolean mIsEmulatingSinglePartyCall = false;
+ /**
+ * Where {@link #mIsEmulatingSinglePartyCall} is {@code true}, contains the
+ * {@link ConferenceParticipantConnection#getUserEntity()} and
+ * {@link ConferenceParticipantConnection#getEndpoint()} of the single participant which this
+ * conference pretends to be.
+ */
+ private Pair<Uri, Uri> mLoneParticipantIdentity = null;
+
+ /**
+ * The {@link ConferenceParticipantConnection#getUserEntity()} and
+ * {@link ConferenceParticipantConnection#getEndpoint()} of the conference host as they appear
+ * in the CEP. This is determined when we scan the first conference event package.
+ * It is possible that this will be {@code null} for carriers which do not include the host
+ * in the CEP.
+ */
+ private Pair<Uri, Uri> mHostParticipantIdentity = null;
public void updateConferenceParticipantsAfterCreation() {
if (mConferenceHost != null) {
@@ -254,19 +285,21 @@
/**
* Initializes a new {@link ImsConference}.
- *
- * @param telephonyConnectionService The connection service responsible for adding new
+ * @param telephonyConnectionService The connection service responsible for adding new
* conferene participants.
* @param conferenceHost The telephony connection hosting the conference.
* @param phoneAccountHandle The phone account handle associated with the conference.
+ * @param featureFlagProxy
*/
public ImsConference(TelecomAccountRegistry telecomAccountRegistry,
- TelephonyConnectionServiceProxy telephonyConnectionService,
- TelephonyConnection conferenceHost, PhoneAccountHandle phoneAccountHandle) {
+ TelephonyConnectionServiceProxy telephonyConnectionService,
+ TelephonyConnection conferenceHost, PhoneAccountHandle phoneAccountHandle,
+ FeatureFlagProxy featureFlagProxy) {
super(phoneAccountHandle);
mTelecomAccountRegistry = telecomAccountRegistry;
+ mFeatureFlagProxy = featureFlagProxy;
// Specify the connection time of the conference to be the connection time of the original
// connection.
@@ -561,16 +594,25 @@
}
/**
- * Updates the manage conference capability of the conference. Where there are one or more
- * conference event package participants, the conference management is permitted. Where there
- * are no conference event package participants, conference management is not permitted.
+ * Updates the manage conference capability of the conference.
+ *
+ * The following cases are handled:
+ * <ul>
+ * <li>There is only a single participant in the conference -- manage conference is
+ * disabled.</li>
+ * <li>There is more than one participant in the conference -- manage conference is
+ * enabled.</li>
+ * <li>No conference event package data is available -- manage conference is disabled.</li>
+ * </ul>
* <p>
* Note: We add and remove {@link Connection#CAPABILITY_CONFERENCE_HAS_NO_CHILDREN} to ensure
* that the conference is represented appropriately on Bluetooth devices.
*/
private void updateManageConference() {
boolean couldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
- boolean canManageConference = !mConferenceParticipantConnections.isEmpty();
+ boolean canManageConference = mFeatureFlagProxy.isUsingSinglePartyCallEmulation()
+ ? mConferenceParticipantConnections.size() > 1
+ : mConferenceParticipantConnections.size() != 0;
Log.v(this, "updateManageConference was :%s is:%s", couldManageConference ? "Y" : "N",
canManageConference ? "Y" : "N");
@@ -649,7 +691,8 @@
* @param parent The connection which was notified of the conference participant.
* @param participants The conference participant information.
*/
- private void handleConferenceParticipantsUpdate(
+ @VisibleForTesting
+ public void handleConferenceParticipantsUpdate(
TelephonyConnection parent, List<ConferenceParticipant> participants) {
if (participants == null) {
@@ -668,64 +711,102 @@
// 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) {
+ int oldParticipantCount = mConferenceParticipantConnections.size();
boolean newParticipantsAdded = false;
boolean oldParticipantsRemoved = false;
ArrayList<ConferenceParticipant> newParticipants = new ArrayList<>(participants.size());
HashSet<Pair<Uri,Uri>> participantUserEntities = new HashSet<>(participants.size());
- // Add any new participants and update existing.
- for (ConferenceParticipant participant : participants) {
- Pair<Uri,Uri> userEntity = new Pair<>(participant.getHandle(),
- participant.getEndpoint());
+ // 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.
+ boolean isSinglePartyConference = participants.stream()
+ .filter(p -> {
+ Pair<Uri, Uri> pIdent = new Pair<>(p.getHandle(), p.getEndpoint());
+ return !Objects.equals(mHostParticipantIdentity, pIdent);
+ })
+ .count() == 1;
- 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;
+ // We will only process the CEP data if:
+ // 1. We're not emulating a single party call.
+ // 2. We're emulating a single party call and the CEP contains more than just the
+ // single party
+ if ((mIsEmulatingSinglePartyCall && !isSinglePartyConference) ||
+ !mIsEmulatingSinglePartyCall) {
+ // Add any new participants and update existing.
+ for (ConferenceParticipant participant : participants) {
+ Pair<Uri, Uri> userEntity = new Pair<>(participant.getHandle(),
+ participant.getEndpoint());
+
+ 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;
+ } else {
+ // Track the identity of the conference host; its useful to know when
+ // we look at the CEP in the future.
+ mHostParticipantIdentity = userEntity;
+ }
+ } else {
+ ConferenceParticipantConnection connection =
+ mConferenceParticipantConnections.get(userEntity);
+ Log.i(this,
+ "handleConferenceParticipantsUpdate: updateState, participant = %s",
+ participant);
+ connection.updateState(participant.getState());
+ connection.setVideoState(parent.getVideoState());
}
- } else {
- ConferenceParticipantConnection connection =
- mConferenceParticipantConnections.get(userEntity);
- Log.i(this, "handleConferenceParticipantsUpdate: updateState, participant = %s",
- participant);
- connection.updateState(participant.getState());
- connection.setVideoState(parent.getVideoState());
+ }
+
+ // 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(new Pair<>(
+ newParticipant.getHandle(),
+ newParticipant.getEndpoint()));
+ connection.updateState(newParticipant.getState());
+ connection.setVideoState(parent.getVideoState());
+ }
+ }
+
+ // Finally, remove any participants from the conference that no longer exist in the
+ // conference event package data.
+ Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator =
+ mConferenceParticipantConnections.entrySet().iterator();
+ while (entryIterator.hasNext()) {
+ Map.Entry<Pair<Uri, 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;
+ }
}
}
- // 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(new Pair<>(
- newParticipant.getHandle(),
- newParticipant.getEndpoint()));
- connection.updateState(newParticipant.getState());
- connection.setVideoState(parent.getVideoState());
- }
- }
-
- // Finally, remove any participants from the conference that no longer exist in the
- // conference event package data.
- Iterator<Map.Entry<Pair<Uri, Uri>, ConferenceParticipantConnection>> entryIterator =
- mConferenceParticipantConnections.entrySet().iterator();
- while (entryIterator.hasNext()) {
- Map.Entry<Pair<Uri, 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;
+ int newParticipantCount = mConferenceParticipantConnections.size();
+ Log.v(this, "handleConferenceParticipantsUpdate: oldParticipantCount=%d, "
+ + "newParticipantcount=%d", oldParticipantCount, newParticipantCount);
+ // If the single party call emulation fature flag is enabled, we can potentially treat
+ // the conference as a single party call when there is just one participant.
+ if (mFeatureFlagProxy.isUsingSinglePartyCallEmulation()) {
+ if (oldParticipantCount > 1 && newParticipantCount == 1) {
+ // If number of participants goes to 1, emulate a single party call.
+ startEmulatingSinglePartyCall();
+ } else if (mIsEmulatingSinglePartyCall && !isSinglePartyConference) {
+ // Number of participants increased, so stop emulating a single party call.
+ stopEmulatingSinglePartyCall();
}
}
@@ -738,6 +819,89 @@
}
/**
+ * Called after {@link #startEmulatingSinglePartyCall()} to cause the conference to appear as
+ * if it is a conference again.
+ * 1. Tell telecom we're a conference again.
+ * 2. Restore {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
+ * 3. Null out the name/address.
+ */
+ private void stopEmulatingSinglePartyCall() {
+ Log.i(this, "stopEmulatingSinglePartyCall: conference now has more than one"
+ + " participant; make it look conference-like again.");
+ mIsEmulatingSinglePartyCall = false;
+
+ if (mCouldManageConference) {
+ int currentCapabilities = getConnectionCapabilities();
+ currentCapabilities |= Connection.CAPABILITY_MANAGE_CONFERENCE;
+ setConnectionCapabilities(currentCapabilities);
+ }
+
+ // Null out the address/name so it doesn't look like a single party call
+ setAddress(null, TelecomManager.PRESENTATION_UNKNOWN);
+ setCallerDisplayName(null, TelecomManager.PRESENTATION_UNKNOWN);
+
+ // Copy the conference connect time back to the previous lone participant.
+ ConferenceParticipantConnection loneParticipant =
+ mConferenceParticipantConnections.get(mLoneParticipantIdentity);
+ if (loneParticipant != null) {
+ Log.d(this,
+ "stopEmulatingSinglePartyCall: restored lone participant connect time");
+ loneParticipant.setConnectTimeMillis(getConnectionTime());
+ loneParticipant.setConnectionStartElapsedRealTime(getConnectionStartElapsedRealTime());
+ }
+
+ // Tell Telecom its a conference again.
+ setConferenceState(true);
+ }
+
+ /**
+ * Called when a conference drops to a single participant. Causes this conference to present
+ * itself to Telecom as if it was a single party call.
+ * 1. Remove the participant from Telecom and from local tracking; when we get a new CEP in
+ * the future we'll just re-add the participant anyways.
+ * 2. Tell telecom we're not a conference.
+ * 3. Remove {@link Connection#CAPABILITY_MANAGE_CONFERENCE} capability.
+ * 4. Set the name/address to that of the single participant.
+ */
+ private void startEmulatingSinglePartyCall() {
+ Log.i(this, "startEmulatingSinglePartyCall: conference has a single "
+ + "participant; downgrade to single party call.");
+
+ mIsEmulatingSinglePartyCall = true;
+ Iterator<ConferenceParticipantConnection> valueIterator =
+ mConferenceParticipantConnections.values().iterator();
+ if (valueIterator.hasNext()) {
+ ConferenceParticipantConnection entry = valueIterator.next();
+
+ // Set the conference name/number to that of the remaining participant.
+ setAddress(entry.getAddress(), entry.getAddressPresentation());
+ setCallerDisplayName(entry.getCallerDisplayName(),
+ entry.getCallerDisplayNamePresentation());
+ setConnectionStartElapsedRealTime(entry.getConnectElapsedTimeMillis());
+ setConnectionTime(entry.getConnectTimeMillis());
+ mLoneParticipantIdentity = new Pair<>(entry.getUserEntity(), entry.getEndpoint());
+
+ // Remove the participant from Telecom. It'll get picked up in a future CEP update
+ // again anyways.
+ entry.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED,
+ "EMULATING_SINGLE_CALL"));
+ entry.removeConnectionListener(mParticipantListener);
+ mTelephonyConnectionService.removeConnection(entry);
+ removeConnection(entry);
+ valueIterator.remove();
+ }
+
+ // Have Telecom pretend its not a conference.
+ setConferenceState(false);
+
+ // Remove manage conference capability.
+ mCouldManageConference = can(Connection.CAPABILITY_MANAGE_CONFERENCE);
+ int currentCapabilities = getConnectionCapabilities();
+ currentCapabilities &= ~Connection.CAPABILITY_MANAGE_CONFERENCE;
+ setConnectionCapabilities(currentCapabilities);
+ }
+
+ /**
* Creates a new {@link ConferenceParticipantConnection} to represent a
* {@link ConferenceParticipant}.
* <p>
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 971dd7b..9902700 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -93,6 +93,8 @@
*/
private final TelephonyConnectionServiceProxy mConnectionService;
+ private final ImsConference.FeatureFlagProxy mFeatureFlagProxy;
+
/**
* List of known {@link TelephonyConnection}s.
*/
@@ -110,11 +112,14 @@
* Creates a new instance of the Ims conference controller.
*
* @param connectionService The current connection service.
+ * @param featureFlagProxy
*/
public ImsConferenceController(TelecomAccountRegistry telecomAccountRegistry,
- TelephonyConnectionServiceProxy connectionService) {
+ TelephonyConnectionServiceProxy connectionService,
+ ImsConference.FeatureFlagProxy featureFlagProxy) {
mConnectionService = connectionService;
mTelecomAccountRegistry = telecomAccountRegistry;
+ mFeatureFlagProxy = featureFlagProxy;
}
/**
@@ -372,7 +377,7 @@
}
ImsConference conference = new ImsConference(mTelecomAccountRegistry, mConnectionService,
- conferenceHostConnection, phoneAccountHandle);
+ conferenceHostConnection, phoneAccountHandle, mFeatureFlagProxy);
conference.setState(conferenceHostConnection.getState());
conference.addListener(mConferenceListener);
conference.updateConferenceParticipantsAfterCreation();
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index c267835..37c5d7c 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -67,7 +67,7 @@
* Owns all data we have registered with Telecom including handling dynamic addition and
* removal of SIMs and SIP accounts.
*/
-public final class TelecomAccountRegistry {
+public class TelecomAccountRegistry {
private static final boolean DBG = false; /* STOP SHIP if true */
// This icon is the one that is used when the Slot ID that we have for a particular SIM
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 00b24e9..ab9e211 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -41,6 +41,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
@@ -145,7 +146,10 @@
new CdmaConferenceController(this);
private final ImsConferenceController mImsConferenceController =
new ImsConferenceController(TelecomAccountRegistry.getInstance(this),
- mTelephonyConnectionServiceProxy);
+ mTelephonyConnectionServiceProxy,
+ // FeatureFlagProxy; used to determine if standalone call emulation is enabled.
+ // TODO: Move to carrier config
+ () -> true);
private ComponentName mExpectedComponentName = null;
private RadioOnHelper mRadioOnHelper;
diff --git a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
index 229bdee..aa832aa 100644
--- a/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
+++ b/tests/src/com/android/services/telephony/ImsConferenceControllerTest.java
@@ -47,6 +47,9 @@
private TelecomAccountRegistry mTelecomAccountRegistry;
+ @Mock
+ private TelecomAccountRegistry mMockTelecomAccountRegistry;
+
private TestTelephonyConnection mTestTelephonyConnectionA;
private TestTelephonyConnection mTestTelephonyConnectionB;
@@ -63,7 +66,7 @@
mTestTelephonyConnectionB = new TestTelephonyConnection();
mControllerTest = new ImsConferenceController(mTelecomAccountRegistry,
- mMockTelephonyConnectionServiceProxy);
+ mMockTelephonyConnectionServiceProxy, () -> false);
}
/**
@@ -78,7 +81,6 @@
@Test
@SmallTest
public void testConferenceable() {
-
mControllerTest.add(mTestTelephonyConnectionB);
mControllerTest.add(mTestTelephonyConnectionA);
@@ -112,7 +114,6 @@
@Test
@SmallTest
public void testMergeMultiPartyCalls() {
-
when(mTestTelephonyConnectionA.mMockRadioConnection.getPhoneType())
.thenReturn(PhoneConstants.PHONE_TYPE_IMS);
when(mTestTelephonyConnectionB.mMockRadioConnection.getPhoneType())
diff --git a/tests/src/com/android/services/telephony/ImsConferenceTest.java b/tests/src/com/android/services/telephony/ImsConferenceTest.java
new file mode 100644
index 0000000..56a6240
--- /dev/null
+++ b/tests/src/com/android/services/telephony/ImsConferenceTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.services.telephony;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.times;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.net.Uri;
+import android.os.Looper;
+import android.telecom.ConferenceParticipant;
+import android.telecom.Connection;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+
+public class ImsConferenceTest {
+ @Mock
+ private TelephonyConnectionServiceProxy mMockTelephonyConnectionServiceProxy;
+
+ @Mock
+ private TelecomAccountRegistry mMockTelecomAccountRegistry;
+
+ @Mock
+ private com.android.internal.telephony.Connection mOriginalConnection;
+
+ private TestTelephonyConnection mConferenceHost;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ mConferenceHost = new TestTelephonyConnection();
+ mConferenceHost.setManageImsConferenceCallSupported(true);
+ }
+
+ @Test
+ @SmallTest
+ public void testSinglePartyEmulation() {
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> true /* featureFlagProxy */);
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Because we're pretending its a single party, there should be no participants any more.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(0, imsConference.getNumberOfParticipants());
+
+ // Back to 2!
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+ }
+
+ @Test
+ @SmallTest
+ public void testNormalConference() {
+ ImsConference imsConference = new ImsConference(mMockTelecomAccountRegistry,
+ mMockTelephonyConnectionServiceProxy, mConferenceHost,
+ null /* phoneAccountHandle */, () -> false /* featureFlagProxy */);
+
+ ConferenceParticipant participant1 = new ConferenceParticipant(
+ Uri.parse("tel:6505551212"),
+ "A",
+ Uri.parse("sip:6505551212@testims.com"),
+ Connection.STATE_ACTIVE);
+ ConferenceParticipant participant2 = new ConferenceParticipant(
+ Uri.parse("tel:6505551213"),
+ "A",
+ Uri.parse("sip:6505551213@testims.com"),
+ Connection.STATE_ACTIVE);
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1, participant2));
+ assertEquals(2, imsConference.getNumberOfParticipants());
+
+ // Not emulating single party; should still be one.
+ imsConference.handleConferenceParticipantsUpdate(mConferenceHost,
+ Arrays.asList(participant1));
+ assertEquals(1, imsConference.getNumberOfParticipants());
+ }
+}
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index 39e4579..c064ef6 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -17,10 +17,12 @@
package com.android.services.telephony;
import android.content.Context;
+import android.content.res.Resources;
import android.os.Bundle;
import android.telecom.PhoneAccountHandle;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -28,6 +30,7 @@
import com.android.internal.telephony.Call;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -50,6 +53,9 @@
@Mock
Context mMockContext;
+ @Mock
+ Resources mMockResources;
+
private Phone mMockPhone;
private int mNotifyPhoneAccountChangedCount = 0;
private List<String> mLastConnectionEvents = new ArrayList<>();
@@ -66,14 +72,18 @@
mMockPhone = mock(Phone.class);
mMockContext = mock(Context.class);
+ mOriginalConnection = mock(Connection.class);
// Set up mMockRadioConnection and mMockPhone to contain an active call
when(mMockRadioConnection.getState()).thenReturn(Call.State.ACTIVE);
when(mMockRadioConnection.getCall()).thenReturn(mMockCall);
+ when(mMockRadioConnection.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_IMS);
doNothing().when(mMockRadioConnection).addListener(any(Connection.Listener.class));
doNothing().when(mMockRadioConnection).addPostDialListener(
any(Connection.PostDialListener.class));
when(mMockPhone.getRingingCall()).thenReturn(mMockCall);
- when(mMockPhone.getContext()).thenReturn(null);
+ when(mMockPhone.getContext()).thenReturn(mMockContext);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getBoolean(anyInt())).thenReturn(false);
when(mMockPhone.getDefaultPhone()).thenReturn(mMockPhone);
when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
when(mMockCall.getPhone()).thenReturn(mMockPhone);