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

Commit e4c29fcc authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Change optional codec status when selectable codec is valid"

parents 69ee4e7e 076bac54
Loading
Loading
Loading
Loading
+22 −8
Original line number Diff line number Diff line
@@ -863,7 +863,8 @@ public class A2dpService extends ProfileService {
     * @param sameAudioFeedingParameters if true the audio feeding parameters
     * haven't been changed
     */
    void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
    @VisibleForTesting
    public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
                            boolean sameAudioFeedingParameters) {
        // Log codec config and capability metrics
        BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
@@ -1015,9 +1016,17 @@ public class A2dpService extends ProfileService {
        }
    }

    private void updateOptionalCodecsSupport(BluetoothDevice device) {

    /**
     * Update and initiate optional codec status change to native.
     *
     * @param device the device to change optional codec status
     */
    @VisibleForTesting
    public void updateOptionalCodecsSupport(BluetoothDevice device) {
        int previousSupport = getSupportsOptionalCodecs(device);
        boolean supportsOptional = false;
        boolean hasMandatoryCodec = false;

        synchronized (mStateMachines) {
            A2dpStateMachine sm = mStateMachines.get(device);
@@ -1027,13 +1036,22 @@ public class A2dpService extends ProfileService {
            BluetoothCodecStatus codecStatus = sm.getCodecStatus();
            if (codecStatus != null) {
                for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
                    if (!config.isMandatoryCodec()) {
                    if (config.isMandatoryCodec()) {
                        hasMandatoryCodec = true;
                    } else {
                        supportsOptional = true;
                        break;
                    }
                }
            }
        }
        if (!hasMandatoryCodec) {
            // Mandatory codec(SBC) is not selectable. It could be caused by the remote device
            // select codec before native finish get codec capabilities. Stop use this codec
            // status as the reference to support/enable optional codecs.
            Log.i(TAG, "updateOptionalCodecsSupport: Mandatory codec is not selectable.");
            return;
        }

        if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
                || supportsOptional != (previousSupport
                                    == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1062,10 +1080,6 @@ public class A2dpService extends ProfileService {
        }
        synchronized (mStateMachines) {
            if (toState == BluetoothProfile.STATE_CONNECTED) {
                // Each time a device connects, we want to re-check if it supports optional
                // codecs (perhaps it's had a firmware update, etc.) and save that state if
                // it differs from what we had saved before.
                updateOptionalCodecsSupport(device);
                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
            }
            // Set the active device if only one connected device is supported and it was connected
+26 −2
Original line number Diff line number Diff line
@@ -89,7 +89,8 @@ final class A2dpStateMachine extends StateMachine {

    private A2dpService mA2dpService;
    private A2dpNativeInterface mA2dpNativeInterface;
    private boolean mA2dpOffloadEnabled = false;
    @VisibleForTesting
    boolean mA2dpOffloadEnabled = false;
    private final BluetoothDevice mDevice;
    private boolean mIsPlaying = false;
    private BluetoothCodecStatus mCodecStatus;
@@ -467,6 +468,10 @@ final class A2dpStateMachine extends StateMachine {

            removeDeferredMessages(CONNECT);

            // Each time a device connects, we want to re-check if it supports optional
            // codecs (perhaps it's had a firmware update, etc.) and save that state if
            // it differs from what we had saved before.
            mA2dpService.updateOptionalCodecsSupport(mDevice);
            broadcastConnectionState(mConnectionState, mLastConnectionState);
            // Upon connected, the audio starts out as stopped
            broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -608,8 +613,11 @@ final class A2dpStateMachine extends StateMachine {
    }

    // NOTE: This event is processed in any state
    private void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
    @VisibleForTesting
    void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus) {
        BluetoothCodecConfig prevCodecConfig = null;
        BluetoothCodecStatus prevCodecStatus = mCodecStatus;

        synchronized (this) {
            if (mCodecStatus != null) {
                prevCodecConfig = mCodecStatus.getCodecConfig();
@@ -630,6 +638,12 @@ final class A2dpStateMachine extends StateMachine {
            }
        }

        if (isConnected() && !sameSelectableCodec(prevCodecStatus, mCodecStatus)) {
            // Remote selectable codec could be changed if codec config changed
            // in connected state, we need to re-check optional codec status
            // for this codec change event.
            mA2dpService.updateOptionalCodecsSupport(mDevice);
        }
        if (mA2dpOffloadEnabled) {
            boolean update = false;
            BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -696,6 +710,16 @@ final class A2dpStateMachine extends StateMachine {
        return builder.toString();
    }

    private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus,
            BluetoothCodecStatus newCodecStatus) {
        if (prevCodecStatus == null) {
            return false;
        }
        return BluetoothCodecStatus.sameCapabilities(
                prevCodecStatus.getCodecsSelectableCapabilities(),
                newCodecStatus.getCodecsSelectableCapabilities());
    }

    private static String messageWhatToString(int what) {
        switch (what) {
            case CONNECT:
+18 −7
Original line number Diff line number Diff line
@@ -1073,7 +1073,6 @@ public class A2dpServiceTest {
                        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
                        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                        0, 0, 0, 0);       // Codec-specific fields
        BluetoothCodecConfig codecConfig = codecConfigSbc;

        BluetoothCodecConfig[] codecsLocalCapabilities;
        BluetoothCodecConfig[] codecsSelectableCapabilities;
@@ -1090,24 +1089,36 @@ public class A2dpServiceTest {
            codecsLocalCapabilities[0] = codecConfigSbc;
            codecsSelectableCapabilities[0] = codecConfigSbc;
        }
        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig,
                                                                    codecsLocalCapabilities,
                                                                    codecsSelectableCapabilities);
        BluetoothCodecConfig[] badCodecsSelectableCapabilities;
        badCodecsSelectableCapabilities = new BluetoothCodecConfig[1];
        badCodecsSelectableCapabilities[0] = codecConfigAac;

        BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc,
                codecsLocalCapabilities, codecsSelectableCapabilities);
        BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac,
                codecsLocalCapabilities, badCodecsSelectableCapabilities);

        when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
                .thenReturn(previousSupport);
        when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
                .thenReturn(previousEnabled);

        // Generate connection request from native with bad codec status
        connectDeviceWithCodecStatus(mTestDevice, badCodecStatus);
        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_CONNECTED);

        // Generate connection request from native with good codec status
        connectDeviceWithCodecStatus(mTestDevice, codecStatus);
        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_CONNECTED);

        // Check optional codec status is set properly
        verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs(
                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED);
        verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs(
                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED);
        verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled(
                mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);

        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
                                            BluetoothProfile.STATE_CONNECTED);
    }
}
+104 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static org.mockito.Mockito.*;

import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothCodecConfig;
import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -54,6 +56,9 @@ public class A2dpStateMachineTest {
    private BluetoothDevice mTestDevice;
    private static final int TIMEOUT_MS = 1000;    // 1s

    private BluetoothCodecConfig mCodecConfigSbc;
    private BluetoothCodecConfig mCodecConfigAac;

    @Mock private AdapterService mAdapterService;
    @Mock private A2dpService mA2dpService;
    @Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +77,22 @@ public class A2dpStateMachineTest {
        // Get a device for testing
        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");

        // Set up sample codec config
        mCodecConfigSbc = new BluetoothCodecConfig(
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
            BluetoothCodecConfig.SAMPLE_RATE_44100,
            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
            0, 0, 0, 0);       // Codec-specific fields
        mCodecConfigAac = new BluetoothCodecConfig(
            BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
            BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
            BluetoothCodecConfig.SAMPLE_RATE_48000,
            BluetoothCodecConfig.BITS_PER_SAMPLE_16,
            BluetoothCodecConfig.CHANNEL_MODE_STEREO,
            0, 0, 0, 0);       // Codec-specific fields

        // Set up thread and looper
        mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
        mHandlerThread.start();
@@ -255,4 +276,87 @@ public class A2dpStateMachineTest {
        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
                IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
    }

    /**
     * Test that codec config change been reported to A2dpService properly.
     */
    @Test
    public void testProcessCodecConfigEvent() {
        testProcessCodecConfigEventCase(false);
    }

    /**
     * Test that codec config change been reported to A2dpService properly when
     * A2DP hardware offloading is enabled.
     */
    @Test
    public void testProcessCodecConfigEvent_OffloadEnabled() {
        testProcessCodecConfigEventCase(true);
    }

    /**
     * Helper methold to test processCodecConfigEvent()
     */
    public void testProcessCodecConfigEventCase(boolean offloadEnabled) {
        if (offloadEnabled) {
            mA2dpStateMachine.mA2dpOffloadEnabled = true;
        }

        doNothing().when(mA2dpService).codecConfigUpdated(any(BluetoothDevice.class),
                any(BluetoothCodecStatus.class), anyBoolean());
        doNothing().when(mA2dpService).updateOptionalCodecsSupport(any(BluetoothDevice.class));
        allowConnection(true);

        BluetoothCodecConfig[] codecsSelectableSbc;
        codecsSelectableSbc = new BluetoothCodecConfig[1];
        codecsSelectableSbc[0] = mCodecConfigSbc;

        BluetoothCodecConfig[] codecsSelectableSbcAac;
        codecsSelectableSbcAac = new BluetoothCodecConfig[2];
        codecsSelectableSbcAac[0] = mCodecConfigSbc;
        codecsSelectableSbcAac[1] = mCodecConfigAac;

        BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
                codecsSelectableSbcAac, codecsSelectableSbc);
        BluetoothCodecStatus codecStatusSbcAndSbcAac = new BluetoothCodecStatus(mCodecConfigSbc,
                codecsSelectableSbcAac, codecsSelectableSbcAac);
        BluetoothCodecStatus codecStatusAacAndSbcAac = new BluetoothCodecStatus(mCodecConfigAac,
                codecsSelectableSbcAac, codecsSelectableSbcAac);

        // Set default codec status when device disconnected
        // Selected codec = SBC, selectable codec = SBC
        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbc, false);

        // Inject an event to change state machine to connected state
        A2dpStackEvent connStCh =
                new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        connStCh.device = mTestDevice;
        connStCh.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED;
        mA2dpStateMachine.sendMessage(A2dpStateMachine.STACK_EVENT, connStCh);

        // Verify that the expected number of broadcasts are executed:
        // - two calls to broadcastConnectionState(): Disconnected -> Conecting -> Connected
        // - one call to broadcastAudioState() when entering Connected state
        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
        verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(intentArgument2.capture(),
                anyString());

        // Verify that state machine update optional codec when enter connected state
        verify(mA2dpService, times(1)).updateOptionalCodecsSupport(mTestDevice);

        // Change codec status when device connected.
        // Selected codec = SBC, selectable codec = SBC+AAC
        mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbcAac);
        if (!offloadEnabled) {
            verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusSbcAndSbcAac, true);
        }
        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);

        // Update selected codec with selectable codec unchanged.
        // Selected codec = AAC, selectable codec = SBC+AAC
        mA2dpStateMachine.processCodecConfigEvent(codecStatusAacAndSbcAac);
        verify(mA2dpService).codecConfigUpdated(mTestDevice, codecStatusAacAndSbcAac, false);
        verify(mA2dpService, times(2)).updateOptionalCodecsSupport(mTestDevice);
    }
}