Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 472ac7e7 authored by Nitin Jadhav's avatar Nitin Jadhav Committed by Nitin Jadhav (xWF)
Browse files

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
parent b9baba83
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
@@ -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;
    }
+119 −0
Original line number Diff line number Diff line
@@ -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;
@@ -978,6 +981,122 @@ public class BluetoothInCallServiceTest {
                        anyInt());
    }

    @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());