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

Commit 319559ff authored by Pranav Madapurmath's avatar Pranav Madapurmath
Browse files

Update preferred device strategy usage

Sync the preferred device strategy with the current communication device
if the corresponding values differ. Also ensure that we only use this
value at the start of a call. We have been observing many issues with
this API which are affecting the audio routing during calls and causing
audio to route incorrectly.

Bug: 377345692
Test: atest CallAudioRouteControllerTest
Test: Manual log verification
Flag: com.android.server.telecom.flags.update_preferred_audio_device_logic

Change-Id: I9986dccbc4b68830f156fff0b31adcc6a0d100dd
parent 223370c8
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -184,3 +184,14 @@ flag {
    purpose: PURPOSE_BUGFIX
  }
}

# OWNER=pmadapurmath TARGET=25Q3
flag {
  name: "update_preferred_audio_device_logic"
  namespace: "telecom"
  description: "Change the use of preferred device for strategy to only use it at the start of the call and include relevant syncing with AudioManager#getCommunicationDevice"
  bug: "377345692"
  metadata {
    purpose: PURPOSE_BUGFIX
  }
}
+60 −5
Original line number Diff line number Diff line
@@ -119,6 +119,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    private int mCallSupportedRouteMask = -1;
    private boolean mIsScoAudioConnected;
    private boolean mAvailableRoutesUpdated;
    private boolean mUsePreferredDeviceStrategy;
    private AudioDeviceInfo mCurrentCommunicationDevice;
    private final Object mLock = new Object();
    private final TelecomSystem.SyncRoot mTelecomLock;
    private CountDownLatch mAudioOperationsCompleteLatch;
@@ -130,7 +132,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
            try {
                if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
                    if (mAudioManager != null) {
                        AudioDeviceInfo info = mAudioManager.getCommunicationDevice();
                        AudioDeviceInfo info = mFeatureFlags.updatePreferredAudioDeviceLogic()
                                ? getCurrentCommunicationDevice()
                                : mAudioManager.getCommunicationDevice();
                        if ((info != null) &&
                                (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)) {
                            if (mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) {
@@ -204,6 +208,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        mMetricsController = metricsController;
        mFocusType = NO_FOCUS;
        mIsScoAudioConnected = false;
        mUsePreferredDeviceStrategy = true;
        setCurrentCommunicationDevice(null);

        mTelecomLock = callsManager.getLock();
        HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
        handlerThread.start();
@@ -232,10 +239,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        mCommunicationDeviceListener = new AudioManager.OnCommunicationDeviceChangedListener() {
            @Override
            public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
                @AudioRoute.AudioRouteType int audioType = device != null
                        ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.getOrDefault(
                                device.getType(), TYPE_INVALID)
                        : TYPE_INVALID;
                @AudioRoute.AudioRouteType int audioType = getAudioType(device);
                setCurrentCommunicationDevice(device);
                Log.i(this, "onCommunicationDeviceChanged: device (%s), audioType (%d)",
                        device, audioType);
                if (audioType == TYPE_SPEAKER) {
@@ -931,6 +936,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                // Clear pending messages
                mPendingAudioRoute.clearPendingMessages();
                clearRingingBluetoothAddress();
                mUsePreferredDeviceStrategy = true;
            }
            case ACTIVE_FOCUS -> {
                // Route to active baseline route (we may need to change audio route in the case
@@ -948,6 +954,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
                                    mCurrentRoute.getBluetoothAddress())
                            ? mCurrentRoute
                            : getBaseRoute(true, null);
                    // Once we have processed active focus once during the call, we can ignore using
                    // the preferred device strategy.
                    mUsePreferredDeviceStrategy = false;
                    routeTo(true, audioRoute);
                    clearRingingBluetoothAddress();
                }
@@ -1287,6 +1296,22 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
        // Get corresponding audio route
        @AudioRoute.AudioRouteType int type = DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
                deviceAttr.getType());
        AudioDeviceInfo currentCommunicationDevice = null;
        if (mFeatureFlags.updatePreferredAudioDeviceLogic()) {
            currentCommunicationDevice = getCurrentCommunicationDevice();
        }
        // We will default to TYPE_INVALID if the currentCommunicationDevice is null or the type
        // cannot be resolved from the given audio device info.
        int communicationDeviceAudioType = getAudioType(currentCommunicationDevice);
        // Sync the preferred device strategy with the current communication device if there's a
        // valid audio device output set as the preferred device strategy. This will address timing
        // issues between updates made to the preferred device strategy. From the audio fwk
        // standpoint, updates to the communication device take precedent to changes in the
        // preferred device strategy so the former should be used as the source of truth.
        if (type != TYPE_INVALID && communicationDeviceAudioType != TYPE_INVALID
                && communicationDeviceAudioType != type) {
            type = communicationDeviceAudioType;
        }
        if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
            return getBluetoothRoute(type, deviceAttr.getAddress());
        } else {
@@ -1420,6 +1445,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    }

    public AudioRoute getBaseRoute(boolean includeBluetooth, String btAddressToExclude) {
        // Catch-all case for all invocations to this method where we shouldn't be using
        // getPreferredAudioRouteFromStrategy
        if (mFeatureFlags.updatePreferredAudioDeviceLogic() && !mUsePreferredDeviceStrategy) {
            return calculateBaselineRoute(false, includeBluetooth, btAddressToExclude);
        }
        AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
        Log.i(this, "getBaseRoute: preferred audio route is %s", destRoute);
        if (destRoute == null || (destRoute.getBluetoothAddress() != null && (!includeBluetooth
@@ -1694,4 +1724,29 @@ public class CallAudioRouteController implements CallAudioRouteAdapter {
    public CountDownLatch getAudioActiveCompleteLatch() {
        return mAudioActiveCompleteLatch;
    }

    private @AudioRoute.AudioRouteType int getAudioType(AudioDeviceInfo device) {
        return device != null
                ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.getOrDefault(
                device.getType(), TYPE_INVALID)
                : TYPE_INVALID;
    }

    @VisibleForTesting
    public boolean getUsePreferredDeviceStrategy() {
        return mUsePreferredDeviceStrategy;
    }

    @VisibleForTesting
    public void setCurrentCommunicationDevice(AudioDeviceInfo device) {
        synchronized (mLock) {
            mCurrentCommunicationDevice = device;
        }
    }

    public AudioDeviceInfo getCurrentCommunicationDevice() {
        synchronized (mLock) {
            return mCurrentCommunicationDevice;
        }
    }
}
+71 −0
Original line number Diff line number Diff line
@@ -68,6 +68,8 @@ import android.bluetooth.BluetoothLeAudio;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IAudioService;
@@ -101,6 +103,7 @@ import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -276,6 +279,65 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
        assertTrue(mController.getAvailableRoutes().contains(mEarpieceRoute));
    }

    @SmallTest
    @Test
    public void testAudioRouteForPreferredDeviceStrategy() {
        when(mFeatureFlags.updatePreferredAudioDeviceLogic()).thenReturn(true);
        mController.initialize();
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
        waitForRouteActiveStateAndVerify(true);
        // Verify preferred device strategy still needs to be used since audio routing hasn't gone
        // active
        assertTrue(mController.getUsePreferredDeviceStrategy());

        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        waitForHandlerAction(mController.getAdapterHandler(), TEST_TIMEOUT);
        assertTrue(mController.isActive());
        // Verify that we should no longer are using the preferred device strategy once we process
        // active focus switch.
        assertFalse(mController.getUsePreferredDeviceStrategy());
    }

    @SmallTest
    @Test
    public void testAudioRouteCommunicationDeviceSyncWithPreferredDeviceStrategy() {
        when(mFeatureFlags.updatePreferredAudioDeviceLogic()).thenReturn(true);
        mController.initialize();
        // Set up tests so that the current communication device is different from the preferred
        // device for strategy.
        AudioDeviceInfo infoCommunicationDevice = mock(AudioDeviceInfo.class);
        when(infoCommunicationDevice.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
        mController.setCurrentCommunicationDevice(infoCommunicationDevice);
        // Setup mocks to test the preferred device strategy.
        setUpPreferredDeviceMocks();

        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
        waitForRouteActiveStateAndVerify(true);
        mController.sendMessageWithSessionInfo(SPEAKER_ON);
        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
                new HashSet<>());
        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // Verify that routing remains unchanged once active focus is processed (we still check
        // for preferred device strategy). Do note that we still end up using the reported
        // communication device instead as it's not synced with the preferred device).
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        verify(mCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));

        // For sanity, verify that routing falls back on earpiece if focus is switched to active
        // again (we don't try to use the preferred device strategy).
        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
        mController.sendMessageWithSessionInfo(SPEAKER_OFF);
        expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
                new HashSet<>());
        verify(mCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
                any(CallAudioState.class), eq(expectedState));
    }

    @SmallTest
    @Test
    public void testNormalCallRouteToEarpiece() {
@@ -1398,4 +1460,13 @@ public class CallAudioRouteControllerTest extends TelecomTestCase {
            assertEquals(mController.isActive(), expectActive);
        }
    }

    private void setUpPreferredDeviceMocks() {
        AudioProductStrategy s = mock(AudioProductStrategy.class);
        when(s.supportsAudioAttributes(any(AudioAttributes.class))).thenReturn(true);
        AudioDeviceAttributes deviceAttr = mock(AudioDeviceAttributes.class);
        when(mAudioManager.getPreferredDeviceForStrategy(any(AudioProductStrategy.class)))
                .thenReturn(deviceAttr);
        when(deviceAttr.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
    }
}