summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java56
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java119
2 files changed, 175 insertions, 0 deletions
diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index ee7a14cbfb..bd0b40ec4f 100644
--- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -55,6 +55,7 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hfp.BluetoothHeadsetProxy;
import com.android.bluetooth.tbs.BluetoothLeCallControlProxy;
@@ -138,6 +139,8 @@ public class BluetoothInCallService extends InCallService {
private final HashMap<Integer, BluetoothCall> mBluetoothConferenceCallInference =
new HashMap<>();
+ private final HashMap<String, Integer> mConferenceCallClccIndexMap = new HashMap<>();
+
// A queue record the removal order of bluetooth calls
private final Queue<Integer> mBluetoothCallQueue = new ArrayDeque<>();
@@ -705,6 +708,19 @@ public class BluetoothInCallService extends InCallService {
Log.d(TAG, "add inference call with reason: " + cause.getReason());
mBluetoothCallQueue.add(call.getId());
mBluetoothConferenceCallInference.put(call.getId(), call);
+ if (Flags.maintainCallIndexAfterConference()) {
+ // If the disconnect is due to call merge, store the index for future use.
+ if (cause.getReason() != null
+ && cause.getReason().equals("IMS_MERGED_SUCCESSFULLY")) {
+ if (!mConferenceCallClccIndexMap.containsKey(getClccMapKey(call))) {
+ if (call.mClccIndex > -1) {
+ mConferenceCallClccIndexMap.put(
+ getClccMapKey(call), call.mClccIndex);
+ }
+ }
+ }
+ }
+
// queue size limited to 2 because merge operation only happens on 2 calls
// we are only interested in last 2 calls merged
if (mBluetoothCallQueue.size() > 2) {
@@ -723,6 +739,15 @@ public class BluetoothInCallService extends InCallService {
updateHeadsetWithCallState(false /* force */);
+ if (Flags.maintainCallIndexAfterConference() && mConferenceCallClccIndexMap.size() > 0) {
+ int anyActiveCalls = mCallInfo.isNullCall(mCallInfo.getActiveCall()) ? 0 : 1;
+ int numHeldCalls = mCallInfo.getNumHeldCalls();
+ // If no call is active or held clear the hashmap.
+ if (anyActiveCalls == 0 && numHeldCalls == 0) {
+ mConferenceCallClccIndexMap.clear();
+ }
+ }
+
if (mBluetoothLeCallControl != null) {
mBluetoothLeCallControl.onCallRemoved(
call.getTbsCallId(), getTbsTerminationReason(call));
@@ -1073,6 +1098,23 @@ public class BluetoothInCallService extends InCallService {
return availableIndex.first();
}
+ @VisibleForTesting
+ /* Function to extract and return call handle. */
+ private String getClccMapKey(BluetoothCall call) {
+ if (mCallInfo.isNullCall(call) || call.getHandle() == null) {
+ return "";
+ }
+ Uri handle = call.getHandle();
+ String key;
+ if (call.hasProperty(Call.Details.PROPERTY_SELF_MANAGED)) {
+ key = handle.toString() + " self managed " + call.getId();
+ } else {
+ key = handle.toString();
+ }
+ Log.d(TAG, "getClccMapKey Key: " + key);
+ return key;
+ }
+
/**
* Returns the caches index for the specified call. If no such index exists, then an index is
* given (the smallest number starting from 1 that isn't already taken).
@@ -1082,6 +1124,13 @@ public class BluetoothInCallService extends InCallService {
Log.w(TAG, "empty or null call");
return -1;
}
+
+ // Check if the call handle is already stored. Return the previously stored index.
+ if (Flags.maintainCallIndexAfterConference()
+ && mConferenceCallClccIndexMap.containsKey(getClccMapKey(call))) {
+ call.mClccIndex = mConferenceCallClccIndexMap.get(getClccMapKey(call));
+ }
+
if (call.mClccIndex >= 1) {
return call.mClccIndex;
}
@@ -1094,6 +1143,13 @@ public class BluetoothInCallService extends InCallService {
// NOTE: Indexes are removed in {@link #onCallRemoved}.
call.mClccIndex = getNextAvailableClccIndex(index);
+ if (Flags.maintainCallIndexAfterConference()) {
+ // Remove the index from conference hashmap, this can be later added if call merges in
+ // conference
+ mConferenceCallClccIndexMap
+ .entrySet()
+ .removeIf(entry -> entry.getValue() == call.mClccIndex);
+ }
Log.d(TAG, "call " + call.getId() + " CLCC index is " + call.mClccIndex);
return call.mClccIndex;
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
index 4d639e79bf..e568fc7bac 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
@@ -28,6 +28,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telecom.BluetoothCallQualityReport;
import android.telecom.Call;
import android.telecom.Connection;
@@ -44,6 +45,7 @@ import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.hfp.BluetoothHeadsetProxy;
import com.android.bluetooth.tbs.BluetoothLeCallControlProxy;
@@ -92,6 +94,7 @@ public class BluetoothInCallServiceTest {
private BluetoothInCallService mBluetoothInCallService;
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock private BluetoothHeadsetProxy mMockBluetoothHeadset;
@Mock private BluetoothLeCallControlProxy mLeCallControl;
@@ -979,6 +982,122 @@ public class BluetoothInCallServiceTest {
}
@Test
+ public void conferenceLastCallIndexIsMaintained() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MAINTAIN_CALL_INDEX_AFTER_CONFERENCE);
+ doReturn("").when(mMockTelephonyManager).getNetworkCountryIso();
+
+ List<BluetoothCall> calls = new ArrayList<>();
+ doReturn(calls).when(mMockCallInfo).getBluetoothCalls();
+
+ // Call 1 active call is added
+ BluetoothCall activeCall_1 = createActiveCall(UUID.randomUUID());
+ calls.add(activeCall_1);
+ mBluetoothInCallService.onCallAdded(activeCall_1);
+
+ doReturn(Call.STATE_ACTIVE).when(activeCall_1).getState();
+ doReturn(Uri.parse("tel:555-0001")).when(activeCall_1).getHandle();
+ doReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0001")))
+ .when(activeCall_1)
+ .getGatewayInfo();
+
+ // Call 2 holding call is added
+ BluetoothCall activeCall_2 = createHeldCall(UUID.randomUUID());
+ calls.add(activeCall_2);
+ mBluetoothInCallService.onCallAdded(activeCall_2);
+
+ doReturn(Call.STATE_HOLDING).when(activeCall_2).getState();
+ doReturn(true).when(activeCall_2).isIncoming();
+ doReturn(Uri.parse("tel:555-0002")).when(activeCall_2).getHandle();
+ doReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0002")))
+ .when(activeCall_2)
+ .getGatewayInfo();
+
+ // needs to have at least one CLCC response before merge to enable call inference
+ clearInvocations(mMockBluetoothHeadset);
+ mBluetoothInCallService.listCurrentCalls();
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 1, 0, CALL_STATE_ACTIVE, 0, false, "5550001", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 2, 1, CALL_STATE_HELD, 0, false, "5550002", PhoneNumberUtils.TOA_Unknown);
+ calls.clear();
+
+ // calls merged for conference call
+ DisconnectCause cause =
+ new DisconnectCause(DisconnectCause.OTHER, "IMS_MERGED_SUCCESSFULLY");
+ doReturn(cause).when(activeCall_1).getDisconnectCause();
+ doReturn(cause).when(activeCall_2).getDisconnectCause();
+ mBluetoothInCallService.onCallRemoved(activeCall_1, true);
+ mBluetoothInCallService.onCallRemoved(activeCall_2, true);
+
+ BluetoothCall conferenceCall = createActiveCall(UUID.randomUUID());
+ addCallCapability(conferenceCall, Connection.CAPABILITY_MANAGE_CONFERENCE);
+
+ doReturn(Uri.parse("tel:555-1234")).when(conferenceCall).getHandle();
+ doReturn(true).when(conferenceCall).isConference();
+ doReturn(Call.STATE_ACTIVE).when(conferenceCall).getState();
+ doReturn(true).when(conferenceCall).hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE);
+ doReturn(true).when(conferenceCall).isIncoming();
+ doReturn(calls).when(mMockCallInfo).getBluetoothCalls();
+
+ // parent call arrived, but children have not, then do inference on children
+ calls.add(conferenceCall);
+ Assert.assertEquals(calls.size(), 1);
+ mBluetoothInCallService.onCallAdded(conferenceCall);
+
+ clearInvocations(mMockBluetoothHeadset);
+ mBluetoothInCallService.listCurrentCalls();
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown);
+
+ // real children arrive, no change on CLCC response
+ calls.add(activeCall_1);
+ mBluetoothInCallService.onCallAdded(activeCall_1);
+ doReturn(true).when(activeCall_1).isConference();
+ calls.add(activeCall_2);
+ mBluetoothInCallService.onCallAdded(activeCall_2);
+ doReturn(Call.STATE_ACTIVE).when(activeCall_2).getState();
+ doReturn(true).when(activeCall_2).isConference();
+ doReturn(List.of(1, 2)).when(conferenceCall).getChildrenIds();
+
+ clearInvocations(mMockBluetoothHeadset);
+ mBluetoothInCallService.listCurrentCalls();
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 1, 0, CALL_STATE_ACTIVE, 0, true, "5550001", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 2, 1, CALL_STATE_ACTIVE, 0, true, "5550002", PhoneNumberUtils.TOA_Unknown);
+
+ // Call 1 Disconnected and removed from conf
+ doReturn(Call.STATE_DISCONNECTED).when(activeCall_1).getState();
+ cause = new DisconnectCause(DisconnectCause.OTHER);
+ doReturn(cause).when(activeCall_1).getDisconnectCause();
+ mBluetoothInCallService.onCallRemoved(activeCall_1, true);
+ doReturn(false).when(activeCall_1).isConference();
+ calls.remove(activeCall_1);
+ Assert.assertEquals(calls.size(), 2);
+
+ // Call 2 removed from conf
+ doReturn(cause).when(activeCall_2).getDisconnectCause();
+ mBluetoothInCallService.onCallRemoved(activeCall_2, true);
+ doReturn(false).when(activeCall_2).isConference();
+
+ clearInvocations(mMockBluetoothHeadset);
+ mBluetoothInCallService.listCurrentCalls();
+
+ // Index 2 is retained
+ verify(mMockBluetoothHeadset)
+ .clccResponse(
+ 2, 1, CALL_STATE_ACTIVE, 0, false, "5550002", PhoneNumberUtils.TOA_Unknown);
+ }
+
+ @Test
public void queryPhoneState() {
BluetoothCall ringingCall = createRingingCall(UUID.randomUUID());
doReturn(Uri.parse("tel:5550000")).when(ringingCall).getHandle();