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

Commit 0a2fb4b0 authored by Yuyang Huang's avatar Yuyang Huang
Browse files

Use same CLCC index even if call handle changed

Current CLCC index depends on call handle. But Network provider may change call handle during middle of the call. So now we associate index with Call object to resolve the issue.

Test: atest BluetoothInCallServiceTest
Bug: 291653994
Change-Id: I7baee929e29791124f40b48720e1a5c2a8ac49fa
parent 062951ee
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.bluetooth.telephony;

import android.bluetooth.BluetoothLeCallControl;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -48,6 +47,9 @@ public class BluetoothCall {
    private Call mCall;
    private UUID mCallId;

    // An index used to identify calls for CLCC (C* List Current Calls).
    int mClccIndex = -1;

    public Call getCall() {
        return mCall;
    }
+128 −89
Original line number Diff line number Diff line
@@ -59,11 +59,12 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -139,14 +140,9 @@ public class BluetoothInCallService extends InCallService {
    private final HashMap<Integer, BluetoothCall> mBluetoothConferenceCallInference =
            new HashMap<>();

    private final Map<String, Integer> mClccInferenceIndexMap = new HashMap<>();

    // A queue record the removal order of bluetooth calls
    private final Queue<Integer> mBluetoothCallQueue = new ArrayDeque<>();

    // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
    private final Map<String, Integer> mClccIndexMap = new HashMap<>();

    private static BluetoothInCallService sInstance = null;

    public CallInfo mCallInfo = new CallInfo();
@@ -652,11 +648,6 @@ public class BluetoothInCallService extends InCallService {
                Log.d(TAG, "add inference call with reason: " + cause.getReason());
                mBluetoothCallQueue.add(call.getId());
                mBluetoothConferenceCallInference.put(call.getId(), 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) {
@@ -669,12 +660,10 @@ public class BluetoothInCallService extends InCallService {
            if (call.isConference()) {
                Log.d(TAG, "conference call ends, clear inference");
                mBluetoothConferenceCallInference.clear();
                mClccInferenceIndexMap.clear();
                mBluetoothCallQueue.clear();
            }
        }

        mClccIndexMap.remove(getClccMapKey(call));
        updateHeadsetWithCallState(false /* force */);

        if (mBluetoothLeCallControl != null) {
@@ -748,9 +737,7 @@ public class BluetoothInCallService extends InCallService {
        sInstance = null;
        mCallbacks.clear();
        mBluetoothCallHashMap.clear();
        mClccIndexMap.clear();
        mBluetoothConferenceCallInference.clear();
        mClccInferenceIndexMap.clear();
        mBluetoothCallQueue.clear();
        mMaxNumberOfCalls = 0;
    }
@@ -766,10 +753,13 @@ public class BluetoothInCallService extends InCallService {
        boolean isInferenceEnabled =
                DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, false);
        Log.d(TAG, "is conference call inference enabled: " + isInferenceEnabled);
        // either do conference call CLCC index inference or normal conference call
        BluetoothCall conferenceCallChildrenNotReady = null;
        for (BluetoothCall call : calls) {
            if (isInferenceEnabled && call.isConference()
            // find the conference call parent among calls
            if (isInferenceEnabled
                    && call.isConference()
                    && !mBluetoothConferenceCallInference.isEmpty()) {
                SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>();
                Log.d(
                        TAG,
                        "conference call inferred size: "
@@ -777,25 +767,55 @@ public class BluetoothInCallService extends InCallService {
                                + " 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 carrier sends children info, then inference will end when info arrives.
                // If carrier doesn't send children info, then inference won't impact actual value.
                if (call.getChildrenIds().size() >= 2) {
                    mBluetoothConferenceCallInference.clear();
                    break;
                }
                conferenceCallChildrenNotReady = call;
            }
        }
        if (conferenceCallChildrenNotReady != null) {
            SortedMap<Integer, Object[]> clccResponseMap = new TreeMap<>();
            for (BluetoothCall inferredCall : mBluetoothConferenceCallInference.values()) {
                    String clccMapKey = getClccMapKey(inferredCall);
                    if (!mClccInferenceIndexMap.containsKey(clccMapKey)) {
                        Log.w(TAG, "Inference Index Map does not have: " + clccMapKey);
                if (inferredCall.isCallNull() || inferredCall.getHandle() == null) {
                    Log.w(TAG, "inferredCall does not have handle");
                    continue;
                }
                    if (mClccInferenceIndexMap.get(clccMapKey) == null) {
                        Log.w(TAG, "inferred index is null");
                // save the index so later on when real children arrive, index is the same
                int index = inferredCall.mClccIndex;
                if (index == -1) {
                    Log.w(TAG, "inferred index is not valid");
                    continue;
                }
                    int index = mClccInferenceIndexMap.get(clccMapKey);
                    // save the index so later on when real children arrive, index is the same
                    mClccIndexMap.put(clccMapKey, index);

                // associate existing bluetoothCall with inferredCall based on call handle
                for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) {
                    if (bluetoothCall.getHandle() == null) {
                        Log.w(TAG, "call id: " + bluetoothCall.getId() + " handle is null");
                        continue;
                    }
                    if (mTelephonyManager == null) {
                        Log.w(TAG, "mTelephonyManager is null");
                        continue;
                    }
                    boolean isSame =
                            PhoneNumberUtils.areSamePhoneNumber(
                                    bluetoothCall.getHandle().toString(),
                                    inferredCall.getHandle().toString(),
                                    mTelephonyManager.getNetworkCountryIso());
                    if (isSame) {
                        Log.d(
                                TAG,
                                "found conference call children that has same call handle, "
                                        + "call id: "
                                        + bluetoothCall.getId());
                        bluetoothCall.mClccIndex = inferredCall.mClccIndex;
                        break;
                    }
                }

                int direction = inferredCall.isIncoming() ? 1 : 0;
                int state = CALL_STATE_ACTIVE;
                boolean isPartOfConference = true;
@@ -809,15 +829,14 @@ public class BluetoothInCallService extends InCallService {
                if (address != null) {
                    address = PhoneNumberUtils.stripSeparators(address);
                }
                    int addressType =
                            address == null ? -1 : PhoneNumberUtils.toaFromString(address);
                int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
                clccResponseMap.put(
                        index,
                        new Object[] {
                            index, direction, state, 0, isPartOfConference, address, addressType
                        });
            }
                // ensure response is sorted by index
            // sort CLCC response based on index
            for (Object[] response : clccResponseMap.values()) {
                if (response.length < 7) {
                    Log.e(TAG, "clccResponseMap entry too short");
@@ -845,15 +864,18 @@ public class BluetoothInCallService extends InCallService {
            sendClccEndMarker();
            return;
        }
        }

        for (BluetoothCall call : calls) {
            // We don't send the parent conference BluetoothCall to the bluetooth device.
            // We do, however want to send conferences that have no children to the bluetooth
            // device (e.g. IMS Conference).
            boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
            Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
                + ", call.getChildrenIds() size " + call.getChildrenIds().size());
            Log.i(
                    TAG,
                    "sendListOfCalls isConferenceWithNoChildren "
                            + isConferenceWithNoChildren
                            + ", call.getChildrenIds() size "
                            + call.getChildrenIds().size());
            if (!call.isConference() || isConferenceWithNoChildren) {
                sendClccForCall(call, shouldLog);
            }
@@ -985,31 +1007,48 @@ public class BluetoothInCallService extends InCallService {
        return key;
    }

    int getNextAvailableClccIndex(int index) {
        // find the next available smallest index
        SortedSet<Integer> availableIndex = new TreeSet<>();
        for (int i = index; i <= mMaxNumberOfCalls + 1; i++) {
            availableIndex.add(i);
        }
        for (BluetoothCall bluetoothCall : mBluetoothCallHashMap.values()) {
            int callCLCCIndex = bluetoothCall.mClccIndex;
            if (availableIndex.contains(callCLCCIndex)) {
                availableIndex.remove(callCLCCIndex);
            }
        }
        Log.d(TAG, "availableIndex first: " + availableIndex.first());
        return availableIndex.first();
    }

    /**
     * Returns the caches index for the specified call. If no such index exists, then an index is
     * given (smallest number starting from 1 that isn't already taken).
     * given (the smallest number starting from 1 that isn't already taken).
     */
    private int getIndexForCall(BluetoothCall call) {
        String key = getClccMapKey(call);
        if (mClccIndexMap.containsKey(key)) {
            return mClccIndexMap.get(key);
        if (mCallInfo.isNullCall(call)) {
            Log.w(TAG, "empty or null call");
            return -1;
        }
        if (call.mClccIndex >= 1) {
            return call.mClccIndex;
        }

        int index = 1; // Indexes for bluetooth clcc are 1-based.
        if (call.isConference()) {
            index = mMaxNumberOfCalls + 1; // The conference call should have a higher index
            Log.i(TAG,
                  "getIndexForCall for conference call starting from "
                  + mMaxNumberOfCalls);
        }
        while (mClccIndexMap.containsValue(index)) {
            index++;
            Log.i(TAG, "getIndexForCall for conference call starting from " + mMaxNumberOfCalls);
        }

        // NOTE: Indexes are removed in {@link #onCallRemoved}.
        mClccIndexMap.put(key, index);
        return index;
        call.mClccIndex = getNextAvailableClccIndex(index);
        Log.d(TAG, "call " + call.getId() + " CLCC index is " + call.mClccIndex);
        return call.mClccIndex;
    }


    private boolean _processChld(int chld) {
        BluetoothCall activeCall = mCallInfo.getActiveCall();
        BluetoothCall ringingCall = mCallInfo.getRingingOrSimulatedRingingCall();
+50 −0
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ import java.util.concurrent.TimeUnit;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BluetoothInCallServiceTest {
    private static final String TAG = "BluetoothInCallServiceTest";

    private static final int TEST_DTMF_TONE = 0;
    private static final String TEST_ACCOUNT_ADDRESS = "//foo.com/";
@@ -553,6 +554,53 @@ public class BluetoothInCallServiceTest {
                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
    }

    @Test
    public void testListCurrentCallsCallHandleChanged() throws Exception {
        mBluetoothInCallService.mTelephonyManager = mMockTelephonyManager;
        when(mMockTelephonyManager.getNetworkCountryIso()).thenReturn("");

        ArrayList<BluetoothCall> calls = new ArrayList<>();
        when(mMockCallInfo.getBluetoothCalls()).thenReturn(calls);
        BluetoothCall activeCall = createForegroundCall(UUID.randomUUID());
        calls.add(activeCall);
        mBluetoothInCallService.onCallAdded(activeCall);

        when(activeCall.getState()).thenReturn(Call.STATE_ACTIVE);
        when(activeCall.isIncoming()).thenReturn(true);
        when(activeCall.isConference()).thenReturn(false);
        when(activeCall.getHandle()).thenReturn(Uri.parse("tel:2135550000"));
        Log.w(TAG, "call handle" + Uri.parse("tel:2135550000"));
        when(activeCall.getGatewayInfo())
                .thenReturn(new GatewayInfo(null, null, Uri.parse("tel:2135550000")));

        clearInvocations(mMockBluetoothHeadset);
        mBluetoothInCallService.listCurrentCalls();
        verify(mMockBluetoothHeadset)
                .clccResponse(
                        1,
                        1,
                        CALL_STATE_ACTIVE,
                        0,
                        false,
                        "2135550000",
                        PhoneNumberUtils.TOA_Unknown);

        // call handle changed
        when(activeCall.getHandle()).thenReturn(Uri.parse("tel:213-555-0000"));
        clearInvocations(mMockBluetoothHeadset);
        Log.w(TAG, "call handle" + Uri.parse("tel:213-555-0000"));
        mBluetoothInCallService.listCurrentCalls();
        verify(mMockBluetoothHeadset)
                .clccResponse(
                        1,
                        1,
                        CALL_STATE_ACTIVE,
                        0,
                        false,
                        "2135550000",
                        PhoneNumberUtils.TOA_Unknown);
    }

    @Test
    public void testRingingCallClccResponse() throws Exception {
        ArrayList<BluetoothCall> calls = new ArrayList<>();
@@ -771,6 +819,8 @@ public class BluetoothInCallServiceTest {
    @Test
    public void testListCurrentCallsConferenceEmptyChildrenInference() throws Exception {
        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BLUETOOTH, CLCC_INFERENCE, "true", false);
        mBluetoothInCallService.mTelephonyManager = mMockTelephonyManager;
        when(mMockTelephonyManager.getNetworkCountryIso()).thenReturn("");

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