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

Commit 0e259e28 authored by Yuyang Huang's avatar Yuyang Huang Committed by Automerger Merge Worker
Browse files

Merge "Enhance CLCC inference" am: 0269583b

parents e601448e 0269583b
Loading
Loading
Loading
Loading
+84 −28
Original line number Diff line number Diff line
@@ -62,6 +62,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -75,7 +77,7 @@ import java.util.concurrent.Executors;
public class BluetoothInCallService extends InCallService {

    private static final String TAG = "BluetoothInCallService";
    private static final String CLCC_INFERENCE = "ConferenceCallInference";
    @VisibleForTesting static final String CLCC_INFERENCE = "ConferenceCallInference";

    // match up with bthf_call_state_t of bt_hf.h
    private static final int CALL_STATE_ACTIVE = 0;
@@ -164,11 +166,13 @@ public class BluetoothInCallService extends InCallService {
                public void onServiceConnected(int profile, BluetoothProfile proxy) {
                    synchronized (LOCK) {
                        if (profile == BluetoothProfile.HEADSET) {
                            setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
                            setBluetoothHeadset(
                                    new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
                            updateHeadsetWithCallState(true /* force */);
                        } else {
                            setBluetoothLeCallControl(new BluetoothLeCallControlProxy((
                                    BluetoothLeCallControl) proxy));
                            setBluetoothLeCallControl(
                                    new BluetoothLeCallControlProxy(
                                            (BluetoothLeCallControl) proxy));
                            sendTbsCurrentCallsList();
                        }
                    }
@@ -639,9 +643,16 @@ public class BluetoothInCallService extends InCallService {
        if (mBluetoothCallHashMap.containsKey(call.getId())) {
            mBluetoothCallHashMap.remove(call.getId());

            DisconnectCause cause = call.getDisconnectCause();
            if (cause != null && cause.getCode() == DisconnectCause.OTHER) {
                Log.d(TAG, "add inference call with reason: " + cause.getReason());
                mBluetoothCallQueue.add(call.getId());
                mBluetoothConferenceCallInference.put(call.getId(), call);
            mClccInferenceIndexMap.put(getClccMapKey(call), mClccIndexMap.get(getClccMapKey(call)));
                Integer indexValue = mClccIndexMap.get(getClccMapKey(call));
                mClccInferenceIndexMap.put(getClccMapKey(call), indexValue);
                if (indexValue == null) {
                    Log.w(TAG, "CLCC index value is null");
                }
                // 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) {
@@ -650,6 +661,14 @@ public class BluetoothInCallService extends InCallService {
                    mBluetoothConferenceCallInference.remove(callId);
                }
            }
            // As there is at most 1 conference call, so clear inference when parent call ends
            if (call.isConference()) {
                Log.d(TAG, "conference call ends, clear inference");
                mBluetoothConferenceCallInference.clear();
                mClccInferenceIndexMap.clear();
                mBluetoothCallQueue.clear();
            }
        }

        mClccIndexMap.remove(getClccMapKey(call));
        updateHeadsetWithCallState(false /* force */);
@@ -745,18 +764,34 @@ public class BluetoothInCallService extends InCallService {
        Log.d(TAG, "is conference call inference enabled: " + isInferenceEnabled);
        for (BluetoothCall call : calls) {
            if (isInferenceEnabled && call.isConference()
                    && call.getChildrenIds().size() < 2
                    && !mBluetoothConferenceCallInference.isEmpty()) {
                Log.d(TAG, "conference call inferred size: "
                SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>();
                Log.d(
                        TAG,
                        "conference call inferred size: "
                                + mBluetoothConferenceCallInference.size()
                        + "current size: " + mBluetoothCallHashMap.size());
                                + " current size: "
                                + mBluetoothCallHashMap.size());
                // Do conference call inference until at least 2 children arrive
                // If carrier does send children info, then inference will end when info arrives.
                // If carrier does not send children info, then inference won't impact actual value.
                if (call.getChildrenIds().size() >= 2) {
                    mBluetoothConferenceCallInference.clear();
                    break;
                }
                for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) {
                    int index = mClccInferenceIndexMap.get(getClccMapKey(inferredCall));
                    String clccMapKey = getClccMapKey(inferredCall);
                    if (!mClccInferenceIndexMap.containsKey(clccMapKey)) {
                        Log.w(TAG, "Inference Index Map does not have: " + clccMapKey);
                        continue;
                    }
                    if (mClccInferenceIndexMap.get(clccMapKey) == null) {
                        Log.w(TAG, "inferred index is null");
                        continue;
                    }
                    int index = mClccInferenceIndexMap.get(clccMapKey);
                    // save the index so later on when real children arrive, index is the same
                    mClccIndexMap.put(getClccMapKey(inferredCall), index);
                    mClccIndexMap.put(clccMapKey, index);
                    int direction = inferredCall.isIncoming() ? 1 : 0;
                    int state = CALL_STATE_ACTIVE;
                    boolean isPartOfConference = true;
@@ -770,17 +805,38 @@ public class BluetoothInCallService extends InCallService {
                    if (address != null) {
                        address = PhoneNumberUtils.stripSeparators(address);
                    }

                    int addressType =
                            address == null ? -1 : PhoneNumberUtils.toaFromString(address);
                    Log.i(TAG, "sending inferred clcc for BluetoothCall "
                            + index + ", "
                            + direction + ", "
                            + state + ", "
                            + isPartOfConference + ", "
                            + addressType);
                    clccResponseMap.put(
                            index,
                            new Object[] {
                                index, direction, state, 0, isPartOfConference, address, addressType
                            });
                }
                // ensure response is sorted by index
                for (Object[] response : clccResponseMap.values()) {
                    if (response.length < 7) {
                        Log.e(TAG, "clccResponseMap entry too short");
                        continue;
                    }
                    Log.i(
                            TAG,
                            String.format(
                                    "sending inferred clcc for BluetoothCall: index %d, direction"
                                        + " %d, state %d, isPartOfConference %b, addressType %d",
                                    (int) response[0],
                                    (int) response[1],
                                    (int) response[2],
                                    (boolean) response[4],
                                    (int) response[6]));
                    mBluetoothHeadset.clccResponse(
                            index, direction, state, 0, isPartOfConference, address, addressType);
                            (int) response[0],
                            (int) response[1],
                            (int) response[2],
                            (int) response[3],
                            (boolean) response[4],
                            (String) response[5],
                            (int) response[6]);
                }
                sendClccEndMarker();
                return;
+140 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.bluetooth.telephony;

import static com.android.bluetooth.telephony.BluetoothInCallService.CLCC_INFERENCE;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@@ -31,6 +32,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.DeviceConfig;
import android.telecom.BluetoothCallQualityReport;
import android.telecom.Call;
import android.telecom.Connection;
@@ -766,6 +768,144 @@ public class BluetoothInCallServiceTest {
                eq(1), eq(1), eq(0), eq(0), eq(true), eq("5551234"), eq(129));
    }

    @Test
    public void testListCurrentCallsConferenceEmptyChildrenInference() throws Exception {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "true", false);

        ArrayList<BluetoothCall> calls = new ArrayList<>();
        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);

        // active call is added
        BluetoothCall activeCall = createActiveCall(UUID.randomUUID());
        calls.add(activeCall);
        mBluetoothInCallService.onCallAdded(activeCall);

        when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE);
        when(activeCall.isIncoming()).thenReturn(false);
        when(activeCall.isConference()).thenReturn(false);
        when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-0001"));
        when(activeCall.getGatewayInfo())
                .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0001")));

        // holding call is added
        BluetoothCall holdingCall = createHeldCall(UUID.randomUUID());
        calls.add(holdingCall);
        mBluetoothInCallService.onCallAdded(holdingCall);

        when(holdingCall.getState()).thenReturn(Call.STATE_HOLDING);
        when(holdingCall.isIncoming()).thenReturn(true);
        when(holdingCall.isConference()).thenReturn(false);
        when(holdingCall.getHandle()).thenReturn(Uri.parse("tel:555-0002"));
        when(holdingCall.getGatewayInfo())
                .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:555-0002")));

        // 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);
        when(activeCall.getDisconnectCause()).thenReturn(cause);
        when(holdingCall.getDisconnectCause()).thenReturn(cause);
        mBluetoothInCallService.onCallRemoved(activeCall, true);
        mBluetoothInCallService.onCallRemoved(holdingCall, true);

        BluetoothCall conferenceCall = createActiveCall(UUID.randomUUID());
        addCallCapability(conferenceCall, Connection.CAPABILITY_MANAGE_CONFERENCE);

        when(conferenceCall.getHandle()).thenReturn(Uri.parse("tel:555-1234"));
        when(conferenceCall.isConference()).thenReturn(true);
        when(conferenceCall.getState()).thenReturn(Call.STATE_ACTIVE);
        when(conferenceCall.hasProperty(Call.Details.PROPERTY_GENERIC_CONFERENCE)).thenReturn(true);
        when(conferenceCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))
                .thenReturn(false);
        when(conferenceCall.isIncoming()).thenReturn(true);
        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);

        // 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);
        mBluetoothInCallService.onCallAdded(activeCall);
        when(activeCall.isConference()).thenReturn(true);
        calls.add(holdingCall);
        mBluetoothInCallService.onCallAdded(holdingCall);
        when(holdingCall.getState()).thenReturn(Call.STATE_ACTIVE);
        when(holdingCall.isConference()).thenReturn(true);
        when(conferenceCall.getChildrenIds()).thenReturn(new ArrayList<>(Arrays.asList(1, 2)));

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

        // when call is terminated, children first removed, then parent
        cause = new DisconnectCause(DisconnectCause.LOCAL);
        when(activeCall.getDisconnectCause()).thenReturn(cause);
        when(holdingCall.getDisconnectCause()).thenReturn(cause);
        mBluetoothInCallService.onCallRemoved(activeCall, true);
        mBluetoothInCallService.onCallRemoved(holdingCall, true);
        calls.remove(activeCall);
        calls.remove(holdingCall);
        Assert.assertEquals(calls.size(), 1);

        clearInvocations(mMockBluetoothHeadset);
        mBluetoothInCallService.listCurrentCalls();
        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
        verify(mMockBluetoothHeadset, times(1))
                .clccResponse(
                        anyInt(),
                        anyInt(),
                        anyInt(),
                        anyInt(),
                        anyBoolean(),
                        nullable(String.class),
                        anyInt());

        // when parent is removed
        when(conferenceCall.getDisconnectCause()).thenReturn(cause);
        calls.remove(conferenceCall);
        mBluetoothInCallService.onCallRemoved(conferenceCall, true);

        clearInvocations(mMockBluetoothHeadset);
        mBluetoothInCallService.listCurrentCalls();
        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
        verify(mMockBluetoothHeadset, times(1))
                .clccResponse(
                        anyInt(),
                        anyInt(),
                        anyInt(),
                        anyInt(),
                        anyBoolean(),
                        nullable(String.class),
                        anyInt());

        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "false", false);
    }

    @Test
    public void testQueryPhoneState() throws Exception {
        BluetoothCall ringingCall = createRingingCall(UUID.randomUUID());