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

Commit 2ca31925 authored by Pranav Madapurmath's avatar Pranav Madapurmath Committed by Android (Google) Code Review
Browse files

Merge "Respect active BT device for routing and resolve flaky BT timing issues" into main

parents 9206e6d3 aecb9a3d
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -17,6 +17,14 @@ flag {
  bug: "306395598"
}

# OWNER=pmadapurmath TARGET=25Q1
flag {
  name: "resolve_active_bt_routing_and_bt_timing_issue"
  namespace: "telecom"
  description: "Resolve the active BT device routing and flaky timing issues noted in BT routing."
  bug: "372029371"
}

# OWNER=tgunn TARGET=24Q3
flag {
  name: "ensure_audio_mode_updates_on_foreground_call_change"
+23 −5
Original line number Diff line number Diff line
@@ -320,13 +320,13 @@ public class AudioRoute {
            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
        if (active) {
            if (mAudioRouteType == TYPE_SPEAKER) {
                pendingAudioRoute.addMessage(SPEAKER_OFF, null);
            }
            int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                    audioManager);
            if (mAudioRouteType == TYPE_SPEAKER) {
                pendingAudioRoute.addMessage(SPEAKER_OFF, null);
            } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO
                    && result == BluetoothStatusCodes.SUCCESS) {
                // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful.
            if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) {
                pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress);
            }
        }
@@ -407,8 +407,26 @@ public class AudioRoute {
        }

        if (result == BluetoothStatusCodes.SUCCESS) {
            if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) {
                maybeClearConnectedPendingMessages(pendingAudioRoute);
            }
            pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
        }
        return result;
    }

    private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) {
        // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it
        // since and disconnected the device, then remove that message so we aren't waiting for
        // it in the message queue.
        if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
            Log.i(this, "clearCommunicationDevice: Clearing pending "
                    + "BT_AUDIO_CONNECTED messages.");
            pendingAudioRoute.clearPendingMessage(
                    new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress));
        } else if (mAudioRouteType == TYPE_SPEAKER) {
            Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages.");
            pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
        }
    }
}
+39 −4
Original line number Diff line number Diff line
@@ -522,7 +522,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                            + "%s(active=%b)",
                    mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
            // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden.
            if (active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
            if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active
                    && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
                mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
            }
            // override pending route while keep waiting for still pending messages for the
@@ -930,8 +931,26 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    }

    private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
        Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, "
                + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude);
        boolean areExcludedBtAndDestBtSame = btAddressToExclude != null
                && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute()
                .getBluetoothAddress());
        Pair<Integer, String> btDevicePendingMsg =
                new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude);

        // 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
                && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
                .contains(btDevicePendingMsg))) {
            Log.i(this, "BT device with address (%s) is currently connecting/connected. "
                    + "Ignore route switch.");
        } else {
            routeTo(mIsActive, calculateBaselineRoute(includeBluetooth, btAddressToExclude));
        }
    }

    private void handleSpeakerOn() {
        if (isPending()) {
@@ -1322,7 +1341,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            return getMostRecentlyActiveBtRoute(btAddressToExclude);
        }

        List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList();
        List<AudioRoute> bluetoothRoutes = getAvailableBluetoothDevicesForRouting();
        // Traverse the routes from the most recently active recorded devices first.
        AudioRoute nonWatchDeviceRoute = null;
        for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) {
@@ -1341,7 +1360,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                return bluetoothRoutes.get(0);
            }
            // Record the first occurrence of a non-watch device route if found.
            if (!mBluetoothRouteManager.isWatch(device) && nonWatchDeviceRoute == null) {
            if (!mBluetoothRouteManager.isWatch(device)) {
                nonWatchDeviceRoute = route;
                break;
            }
@@ -1351,6 +1370,22 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        return nonWatchDeviceRoute;
    }

    private List<AudioRoute> getAvailableBluetoothDevicesForRouting() {
        List<AudioRoute> bluetoothRoutes = new ArrayList<>(mBluetoothRoutes.keySet());
        if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
            return bluetoothRoutes;
        }
        // Consider the active device (BT_ACTIVE_DEVICE_PRESENT) if it exists first.
        AudioRoute activeDeviceRoute = getArbitraryBluetoothDevice();
        if (activeDeviceRoute != null && (bluetoothRoutes.isEmpty()
                || !bluetoothRoutes.get(bluetoothRoutes.size() - 1).equals(activeDeviceRoute))) {
            Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: active BT device (%s) present."
                    + "Considering this device for selection first.", activeDeviceRoute);
            bluetoothRoutes.add(activeDeviceRoute);
        }
        return bluetoothRoutes;
    }

    /**
     * Returns the most actively reported bluetooth route excluding the passed in route.
     */
+8 −0
Original line number Diff line number Diff line
@@ -130,6 +130,10 @@ public class PendingAudioRoute {
        mPendingMessages.remove(message);
    }

    public Set<Pair<Integer, String>> getPendingMessages() {
        return mPendingMessages;
    }

    public boolean isActive() {
        return mActive;
    }
@@ -146,4 +150,8 @@ public class PendingAudioRoute {
    public void overrideDestRoute(AudioRoute route) {
        mDestRoute = route;
    }

    public FeatureFlags getFeatureFlags() {
        return mFeatureFlags;
    }
}
+123 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.ACTIVE_FOCUS;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED;
import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
import static com.android.server.telecom.CallAudioRouteAdapter.CONNECT_DOCK;
@@ -40,6 +41,8 @@ import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BLUET
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_EARPIECE;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET;
import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAKER;
import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -189,6 +192,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
        when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false);
    }

    @After
@@ -908,6 +912,125 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {

    }

    @SmallTest
    @Test
    public void testMimicVoiceDialWithBt() {
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
        mController.initialize();
        mController.setActive(true);

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

        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED
        mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
                INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
        // Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request.
        mController.setIsScoAudioConnected(true);
        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
        // Verify SCO not disconnected and route stays on connected BT device.
        verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));
    }

    @SmallTest
    @Test
    public void testTransactionalCallBtConnectingAndSwitchCallEndpoint() {
        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
        mController.initialize();
        mController.setActive(true);

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

        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
        // Omit sending BT_AUDIO_CONNECTED to mimic scenario where BT is still connecting and user
        // switches to speaker.
        mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER);
        mController.sendMessageWithSessionInfo(SPEAKER_ON);
        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
                BLUETOOTH_DEVICE_1);

        // Verify SCO disconnected
        verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco();
        // Verify audio properly routes into speaker.
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                        | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));
    }

    @Test
    @SmallTest
    public void testBluetoothRouteToActiveDevice() {
        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 scoDevice =
                BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
        BLUETOOTH_DEVICES.add(scoDevice);
        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
                scoDevice);
        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
                BLUETOOTH_DEVICE_1);
        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
                scoDevice);
        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                        | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Mimic behavior when inactive headset is used to answer the call (i.e. tap headset). In
        // this case, the inactive BT device will become the active device (reported to us from BT
        // stack to controller via BT_ACTIVE_DEVICE_PRESENT).
        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress());
        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
                scoDevice);
        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
                BLUETOOTH_DEVICE_1);
        // Verify audio routed to BLUETOOTH_DEVICE_1
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Now switch call to active focus so that base route can be recalculated.
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
        // Verify that audio is still routed into BLUETOOTH_DEVICE_1 and not the 2nd BT device.
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Clean up BLUETOOTH_DEVICES for subsequent tests.
        BLUETOOTH_DEVICES.remove(scoDevice);
    }

    private void verifyConnectBluetoothDevice(int audioType) {
        mController.initialize();
        mController.setActive(true);