diff options
author | 2024-05-23 06:37:39 +0000 | |
---|---|---|
committer | 2024-09-24 06:09:23 +0000 | |
commit | 472ac7e75cbf24d7cac6607dc19e7da981bcaa7e (patch) | |
tree | 8b181df36631d1d8ab85b265da09d77cb14bfc44 | |
parent | b9baba835fe0001e71a270b0eac6bee5e789d359 (diff) |
Call Index: To Handle HFP index change after call Merge and disconnect
Bug: 318460024
Bug: 345380335
Test: Manual | Make two incomming calls, merge calls & disconnect first call
Test: atest BluetoothInCallServiceTest#conferenceLastCallIndexIsMaintained
Flag: com.android.bluetooth.flags.maintain_call_index_after_conference
Change-Id: Ibf7015d7a250d62641a9e1df417d17d75bad71b6
-rw-r--r-- | android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java | 56 | ||||
-rw-r--r-- | android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java | 119 |
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(); |