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

Commit 68064225 authored by Pranav Madapurmath's avatar Pranav Madapurmath
Browse files

Resolve update system audio route NPE and foreground updates

With the recent addition of handling UPDATE_SYSTEM_AUDIO_ROUTE in the
new call audio routing path, there was a report of a NPE found due to
the CS being unbound during the system audio update. As a result, it
caused a NPE on IConnectionService.onCallEndpointChanged further down
the stack. We should ensure that these updates are done under the
telecom lock.

Following that, also ensure that the audio routing is adjusted to the
foreground call. Currently, CallAudioRouteController considers all
potential audio routes that the device supports but for individual
calls, this is determined via Call#getSupportedAudioRoutes(). We should
ensure that the ICS/CS are notified which routes are available and
adjust the current route based on what the foreground call supports.
When we're not in a call, we can fall back to using the device supported
routes.

Bug: 351977307
Test: atest CallAudioRouteControllerTest
Flag: com.android.server.telecom.flags.use_refactored_audio_route_switching

Change-Id: I5fdd630fb82469783f7d298e25ba3f73910fa2c7
parent f5cf4108
Loading
Loading
Loading
Loading
+86 −19
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private final Handler mHandler;
    private final WiredHeadsetManager mWiredHeadsetManager;
    private Set<AudioRoute> mAvailableRoutes;
    private Set<AudioRoute> mCallSupportedRoutes;
    private AudioRoute mCurrentRoute;
    private AudioRoute mEarpieceWiredRoute;
    private AudioRoute mSpeakerDockRoute;
@@ -104,6 +105,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private StatusBarNotifier mStatusBarNotifier;
    private FeatureFlags mFeatureFlags;
    private int mFocusType;
    private int mCallSupportedRouteMask = -1;
    private boolean mIsScoAudioConnected;
    private final Object mLock = new Object();
    private final TelecomSystem.SyncRoot mTelecomLock;
@@ -314,6 +316,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                            handleExitPendingRoute();
                            break;
                        case UPDATE_SYSTEM_AUDIO_ROUTE:
                            // Based on the available routes for foreground call, adjust routing.
                            updateRouteForForeground();
                            // Force update to notify all ICS/CS.
                            updateCallAudioState(new CallAudioState(mIsMute,
                                    mCallAudioState.getRoute(),
                                    mCallAudioState.getSupportedRouteMask(),
@@ -330,6 +335,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    @Override
    public void initialize() {
        mAvailableRoutes = new HashSet<>();
        mCallSupportedRoutes = new HashSet<>();
        mBluetoothRoutes = new LinkedHashMap<>();
        mActiveDeviceCache = new HashMap<>();
        mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_SCO, null);
@@ -485,7 +491,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    }

    private void routeTo(boolean active, AudioRoute destRoute) {
        if (!destRoute.equals(mStreamingRoute) && !getAvailableRoutes().contains(destRoute)) {
        if (destRoute == null || (!destRoute.equals(mStreamingRoute)
                && !getCallSupportedRoutes().contains(destRoute))) {
            Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
            return;
        }
@@ -510,7 +517,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
                    mIsActive, destRoute, active);
            // route to pending route
            if (getAvailableRoutes().contains(mCurrentRoute)) {
            if (getCallSupportedRoutes().contains(mCurrentRoute)) {
                mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
            } else {
                // Avoid waiting for pending messages for an unavailable route
@@ -841,7 +848,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {

    public void handleSwitchEarpiece() {
        AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
        if (earpieceRoute != null && getAvailableRoutes().contains(earpieceRoute)) {
        if (earpieceRoute != null && getCallSupportedRoutes().contains(earpieceRoute)) {
            routeTo(mIsActive, earpieceRoute);
        } else {
            Log.i(this, "ignore switch earpiece request");
@@ -856,7 +863,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            bluetoothRoute = getArbitraryBluetoothDevice();
            bluetoothDevice = mBluetoothRoutes.get(bluetoothRoute);
        } else {
            for (AudioRoute route : getAvailableRoutes()) {
            for (AudioRoute route : getCallSupportedRoutes()) {
                if (Objects.equals(address, route.getBluetoothAddress())) {
                    bluetoothRoute = route;
                    bluetoothDevice = mBluetoothRoutes.get(route);
@@ -894,7 +901,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {

    private void handleSwitchHeadset() {
        AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
        if (headsetRoute != null && getAvailableRoutes().contains(headsetRoute)) {
        if (headsetRoute != null && getCallSupportedRoutes().contains(headsetRoute)) {
            routeTo(mIsActive, headsetRoute);
        } else {
            Log.i(this, "ignore switch headset request");
@@ -902,7 +909,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    }

    private void handleSwitchSpeaker() {
        if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
        if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute)) {
            routeTo(mIsActive, mSpeakerDockRoute);
        } else {
            Log.i(this, "ignore switch speaker request");
@@ -920,7 +927,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            // Update status bar notification if we are in a call.
            mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
        } else {
            if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
            if (mSpeakerDockRoute != null && getCallSupportedRoutes()
                    .contains(mSpeakerDockRoute)) {
                routeTo(mIsActive, mSpeakerDockRoute);
                // Since the route switching triggered by this message, we need to manually send it
                // again so that we won't stuck in the pending route
@@ -984,7 +992,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        synchronized (mLock) {
            int routeMask = 0;
            Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
            for (AudioRoute route : getAvailableRoutes()) {
            for (AudioRoute route : getCallSupportedRoutes()) {
                routeMask |= ROUTE_MAP.get(route.getType());
                if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
                    BluetoothDevice deviceToAdd = mBluetoothRoutes.get(route);
@@ -1004,6 +1012,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                    }
                }
            }

            updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
                    mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
        }
@@ -1015,7 +1024,50 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                mCallAudioState.getSupportedBluetoothDevices()));
    }

    /**
     * Retrieves the current call's supported audio route and adjusts the audio routing if the
     * current route isn't supported.
     */
    private void updateRouteForForeground() {
        boolean updatedRouteForCall = updateCallSupportedAudioRoutes();
        // Ensure that current call audio state has updated routes for current call.
        if (updatedRouteForCall) {
            mCallAudioState = new CallAudioState(mIsMute, mCallAudioState.getRoute(),
                    mCallSupportedRouteMask, mCallAudioState.getActiveBluetoothDevice(),
                    mCallAudioState.getSupportedBluetoothDevices());
            // Update audio route if foreground call doesn't support the current route.
            if ((mCallSupportedRouteMask & mCallAudioState.getRoute()) == 0) {
                routeTo(mIsActive, getBaseRoute(true, null));
            }
        }
    }

    /**
     * Update supported audio routes for the foreground call if present.
     */
    private boolean updateCallSupportedAudioRoutes() {
        int availableRouteMask = 0;
        Call foregroundCall = mCallsManager.getForegroundCall();
        if (foregroundCall != null) {
            int foregroundCallSupportedRouteMask = foregroundCall.getSupportedAudioRoutes();
            for (AudioRoute route : getAvailableRoutes()) {
                int routeType = ROUTE_MAP.get(route.getType());
                availableRouteMask |= routeType;
                if ((routeType & foregroundCallSupportedRouteMask) == routeType) {
                    mCallSupportedRoutes.add(route);
                }
            }
            mCallSupportedRouteMask = availableRouteMask & foregroundCallSupportedRouteMask;
            return true;
        } else {
            mCallSupportedRoutes.clear();
            mCallSupportedRouteMask = -1;
            return false;
        }
    }

    private void updateCallAudioState(CallAudioState newCallAudioState) {
        synchronized (mTelecomLock) {
            Log.i(this, "updateCallAudioState: updating call audio state to %s", newCallAudioState);
            CallAudioState oldState = mCallAudioState;
            mCallAudioState = newCallAudioState;
@@ -1024,6 +1076,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
            updateAudioStateForTrackedCalls(mCallAudioState);
        }
    }

    private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) {
        Set<Call> calls = mCallsManager.getTrackedCalls();
@@ -1080,11 +1133,17 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        // are only wearables available.
        AudioRoute activeWatchOrNonWatchDeviceRoute =
                getActiveWatchOrNonWatchDeviceRoute(btAddressToExclude);
        if (mBluetoothRoutes.isEmpty() || !includeBluetooth
                || activeWatchOrNonWatchDeviceRoute == null) {
        if ((!mCallSupportedRoutes.isEmpty() && (mCallSupportedRouteMask
                & CallAudioState.ROUTE_BLUETOOTH) == 0) || mBluetoothRoutes.isEmpty()
                || !includeBluetooth || activeWatchOrNonWatchDeviceRoute == null) {
            Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
                    + "available non-BT route.");
            AudioRoute defaultRoute = mEarpieceWiredRoute != null
            boolean callSupportsEarpieceWiredRoute = mCallSupportedRoutes.isEmpty()
                    || mCallSupportedRoutes.contains(mEarpieceWiredRoute);
            // If call supported route doesn't contain earpiece/wired/BT, it should have speaker
            // enabled. Otherwise, no routes would be supported for the call which should never be
            // the case.
            AudioRoute defaultRoute = mEarpieceWiredRoute != null && callSupportsEarpieceWiredRoute
                    ? mEarpieceWiredRoute
                    : mSpeakerDockRoute;
            // Ensure that we default to speaker route if we're in a video call, but disregard it if
@@ -1136,6 +1195,14 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        }
    }

    public Set<AudioRoute> getCallSupportedRoutes() {
        if (mCurrentRoute.equals(mStreamingRoute)) {
            return mStreamingRoutes;
        } else {
            return mCallSupportedRoutes.isEmpty() ? mAvailableRoutes : mCallSupportedRoutes;
        }
    }

    public AudioRoute getCurrentRoute() {
        return mCurrentRoute;
    }
@@ -1155,7 +1222,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        if (destRoute == null || (destRoute.getBluetoothAddress() != null && !includeBluetooth)) {
            destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
        }
        if (destRoute != null && !getAvailableRoutes().contains(destRoute)) {
        if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
            destRoute = null;
        }
        Log.i(this, "getBaseRoute - audio routing to %s", destRoute);
+32 −0
Original line number Diff line number Diff line
@@ -72,6 +72,7 @@ import com.android.server.telecom.AudioRoute;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteController;
import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.PendingAudioRoute;
import com.android.server.telecom.StatusBarNotifier;
@@ -152,6 +153,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        when(mCallsManager.getCurrentUserHandle()).thenReturn(
                new UserHandle(UserHandle.USER_SYSTEM));
        when(mCallsManager.getLock()).thenReturn(mLock);
        when(mCallsManager.getForegroundCall()).thenReturn(mCall);
        when(mBluetoothRouteManager.getDeviceManager()).thenReturn(mBluetoothDeviceManager);
        when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt()))
                .thenReturn(true);
@@ -172,6 +174,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        mController.setCallAudioManager(mCallAudioManager);
        when(mCallAudioManager.getForegroundCall()).thenReturn(mCall);
        when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
        when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
        when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
    }
@@ -799,6 +802,35 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
            any(CallAudioState.class), eq(expectedState));
    }

    @SmallTest
    @Test
    public void testUpdateRouteForForeground() {
        mController.initialize();
        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
                BLUETOOTH_DEVICE_1);

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

        // Ensure that supported routes is updated along with the current route to reflect the
        // foreground call's supported audio routes.
        when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_SPEAKER);
        mController.sendMessageWithSessionInfo(
                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
        mController.sendMessageWithSessionInfo(SPEAKER_ON);
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));
        assertEquals(3, mController.getAvailableRoutes().size());
        assertEquals(1, mController.getCallSupportedRoutes().size());
    }

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