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

Commit 95d0739c authored by Pranav Madapurmath's avatar Pranav Madapurmath
Browse files

Resolve audio not routing to watch issue

When a BTHS and watch are paired with the phone, the call audio was
routing to BTHS instead of the watch when the call was answered on the
watch. Although the resolveActiveBtRoutingAndBtTimingIssue flag was
disabled when this was tested, the issue would still exist in the
flagged changes as well. Revert the changes where we wait to update the
active device once the CallAudioRouteController has finished processing
the requests and update the state immediately.

We should also ensure that when considering the watch device for
routing, also make sure to check it against the active device cache
instead of just considering the current route (it's possible that the
watch hasn't been routed into yet but the active device is the watch).

Another fix involves making sure that we ignore recalculating the
baseline in cases where the pending route or current route isn't already
routed into the BT device that should be excluded.

Bug: 377628039
Test: Manual with BTHS + watch paired and verifying audio routed to
watch when call answered on the watch.
Flag:
com.android.server.telecom.flags.resolve_active_bt_routing_and_bt_timing_issue

Change-Id: I47449430784efb1eda291b58d40dabd5fb8dd125
parent f837d55f
Loading
Loading
Loading
Loading
+25 −14
Original line number Diff line number Diff line
@@ -800,7 +800,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        if (bluetoothRoute != null) {
            Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
                    mIsActive);
            updateActiveBluetoothDevice(new Pair<>(type, deviceAddress));
            routeTo(mIsActive, bluetoothRoute);
        } else {
            Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
@@ -844,10 +843,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            // Fallback to an available route excluding the previously active device.
            routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
        }
        // Clear out the active device for the BT audio type.
        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
            updateActiveBluetoothDevice(new Pair(type, null));
        }
    }

    private void handleMuteChanged(boolean mute) {
@@ -1023,16 +1018,29 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then
        // we know that the device has reconnected or is in the middle of connecting. Ignore routing
        // out of this BT device.
        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && areExcludedBtAndDestBtSame
        boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
                && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
                .contains(btDevicePendingMsg))) {
                .contains(btDevicePendingMsg));
        // Check if the pending audio route or current route is already different from the route
        // including the BT device that should be excluded from route selection.
        boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null
                && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute()
                .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals(
                        mCurrentRoute.getBluetoothAddress())));
        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
            if (isExcludedDeviceConnectingOrConnected) {
                Log.i(this, "BT device with address (%s) is currently connecting/connected. "
                    + "Ignore route switch.");
        } else {
                        + "Ignoring route switch.", btAddressToExclude);
                return;
            } else if (isCurrentOrDestRouteDifferent) {
                Log.i(this, "Current or pending audio route isn't routed to device with address "
                        + "(%s). Ignoring route switch.", btAddressToExclude);
                return;
            }
        }
        routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
                btAddressToExclude));
    }
    }

    private void handleSpeakerOn() {
        if (isPending()) {
@@ -1441,8 +1449,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                continue;
            }
            // Check if the most recently active device is a watch device.
            if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState
                    .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) {
            boolean isActiveDevice = mActiveBluetoothDevice != null
                    && device.getAddress().equals(mActiveBluetoothDevice.second);
            if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device)
                    && (device.equals(mCallAudioState.getActiveBluetoothDevice())
                    || isActiveDevice)) {
                Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s",
                        bluetoothRoutes.get(0));
                return bluetoothRoutes.get(0);
+5 −8
Original line number Diff line number Diff line
@@ -252,17 +252,14 @@ public class BluetoothStateReceiver extends BroadcastReceiver {
            CallAudioRouteController audioRouteController = (CallAudioRouteController)
                    mCallAudioRouteAdapter;
            if (device == null) {
                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
                    audioRouteController.updateActiveBluetoothDevice(
                            new Pair(audioRouteType, null));
                }
                // Update the active device cache immediately.
                audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null));
                mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
                        audioRouteType);
            } else {
                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
                // Update the active device cache immediately.
                audioRouteController.updateActiveBluetoothDevice(
                        new Pair(audioRouteType, device.getAddress()));
                }
                mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                        audioRouteType, device.getAddress());
                if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID
+58 −0
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ import android.media.audiopolicy.AudioProductStrategy;
import android.os.UserHandle;
import android.telecom.CallAudioState;
import android.telecom.VideoProfile;
import android.util.Pair;

import androidx.test.filters.SmallTest;

@@ -1099,6 +1100,63 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
                any(CallAudioState.class), eq(expectedState));
    }

    @Test
    @SmallTest
    public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() {
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
        // Connect first BT device.
        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
        // Connect another BT device.
        String scoDeviceAddress = "00:00:00:00:00:03";
        BluetoothDevice watchDevice =
                BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
        when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true);
        BLUETOOTH_DEVICES.add(watchDevice);

        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
                watchDevice);
        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
                | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Signal that watch is now the active device. This is done in BluetoothStateReceiver and
        // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed.
        mController.updateActiveBluetoothDevice(
                new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress()));
        // Emulate scenario with call answered on watch. Ensure at this point that audio was routed
        // into watch
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
                0, watchDevice);
        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED,
                0, BLUETOOTH_DEVICE_1);
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
                        | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Hardcode signal from BT stack signaling to Telecom that watch is now the active device.
        // This should just be a no-op since audio was already routed when processing active focus.
        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and
        // verify that audio remains routed to the watch and not routed to earpiece (this should
        // be taking into account what the BT active device is as reported to us by the BT stack).
        mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
                INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        BLUETOOTH_DEVICES.remove(watchDevice);
    }


    @Test
    @SmallTest
    public void testAbandonCallAudioFocusAfterCallEnd() {