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

Commit 4eedcac7 authored by Ugo Yu's avatar Ugo Yu Committed by android-build-merger
Browse files

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

am: db22a417

Change-Id: I75eae79df1d8e526461b0de0d89ab4be79086912
parents 0d413eb0 db22a417
Loading
Loading
Loading
Loading
+22 −8
Original line number Original line Diff line number Diff line
@@ -863,7 +863,8 @@ public class A2dpService extends ProfileService {
     * @param sameAudioFeedingParameters if true the audio feeding parameters
     * @param sameAudioFeedingParameters if true the audio feeding parameters
     * haven't been changed
     * haven't been changed
     */
     */
    void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
    @VisibleForTesting
    public void codecConfigUpdated(BluetoothDevice device, BluetoothCodecStatus codecStatus,
                            boolean sameAudioFeedingParameters) {
                            boolean sameAudioFeedingParameters) {
        // Log codec config and capability metrics
        // Log codec config and capability metrics
        BluetoothCodecConfig codecConfig = codecStatus.getCodecConfig();
        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);
        int previousSupport = getSupportsOptionalCodecs(device);
        boolean supportsOptional = false;
        boolean supportsOptional = false;
        boolean hasMandatoryCodec = false;


        synchronized (mStateMachines) {
        synchronized (mStateMachines) {
            A2dpStateMachine sm = mStateMachines.get(device);
            A2dpStateMachine sm = mStateMachines.get(device);
@@ -1027,13 +1036,22 @@ public class A2dpService extends ProfileService {
            BluetoothCodecStatus codecStatus = sm.getCodecStatus();
            BluetoothCodecStatus codecStatus = sm.getCodecStatus();
            if (codecStatus != null) {
            if (codecStatus != null) {
                for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
                for (BluetoothCodecConfig config : codecStatus.getCodecsSelectableCapabilities()) {
                    if (!config.isMandatoryCodec()) {
                    if (config.isMandatoryCodec()) {
                        hasMandatoryCodec = true;
                    } else {
                        supportsOptional = true;
                        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
        if (previousSupport == BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN
                || supportsOptional != (previousSupport
                || supportsOptional != (previousSupport
                                    == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
                                    == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED)) {
@@ -1062,10 +1080,6 @@ public class A2dpService extends ProfileService {
        }
        }
        synchronized (mStateMachines) {
        synchronized (mStateMachines) {
            if (toState == BluetoothProfile.STATE_CONNECTED) {
            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);
                MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP);
            }
            }
            // Set the active device if only one connected device is supported and it was connected
            // Set the active device if only one connected device is supported and it was connected
+26 −2
Original line number Original line Diff line number Diff line
@@ -89,7 +89,8 @@ final class A2dpStateMachine extends StateMachine {


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


            removeDeferredMessages(CONNECT);
            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);
            broadcastConnectionState(mConnectionState, mLastConnectionState);
            // Upon connected, the audio starts out as stopped
            // Upon connected, the audio starts out as stopped
            broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
            broadcastAudioState(BluetoothA2dp.STATE_NOT_PLAYING,
@@ -608,8 +613,11 @@ final class A2dpStateMachine extends StateMachine {
    }
    }


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

        synchronized (this) {
        synchronized (this) {
            if (mCodecStatus != null) {
            if (mCodecStatus != null) {
                prevCodecConfig = mCodecStatus.getCodecConfig();
                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) {
        if (mA2dpOffloadEnabled) {
            boolean update = false;
            boolean update = false;
            BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
            BluetoothCodecConfig newCodecConfig = mCodecStatus.getCodecConfig();
@@ -696,6 +710,16 @@ final class A2dpStateMachine extends StateMachine {
        return builder.toString();
        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) {
    private static String messageWhatToString(int what) {
        switch (what) {
        switch (what) {
            case CONNECT:
            case CONNECT:
+18 −7
Original line number Original line Diff line number Diff line
@@ -1073,7 +1073,6 @@ public class A2dpServiceTest {
                        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
                        BluetoothCodecConfig.BITS_PER_SAMPLE_16,
                        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                        BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                        0, 0, 0, 0);       // Codec-specific fields
                        0, 0, 0, 0);       // Codec-specific fields
        BluetoothCodecConfig codecConfig = codecConfigSbc;


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

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


        when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
        when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice))
                .thenReturn(previousSupport);
                .thenReturn(previousSupport);
        when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
        when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice))
                .thenReturn(previousEnabled);
                .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);
        connectDeviceWithCodecStatus(mTestDevice, codecStatus);
        generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED,
                BluetoothProfile.STATE_CONNECTED);


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

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


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


    private BluetoothCodecConfig mCodecConfigSbc;
    private BluetoothCodecConfig mCodecConfigAac;

    @Mock private AdapterService mAdapterService;
    @Mock private AdapterService mAdapterService;
    @Mock private A2dpService mA2dpService;
    @Mock private A2dpService mA2dpService;
    @Mock private A2dpNativeInterface mA2dpNativeInterface;
    @Mock private A2dpNativeInterface mA2dpNativeInterface;
@@ -72,6 +77,22 @@ public class A2dpStateMachineTest {
        // Get a device for testing
        // Get a device for testing
        mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
        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
        // Set up thread and looper
        mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
        mHandlerThread = new HandlerThread("A2dpStateMachineTestHandlerThread");
        mHandlerThread.start();
        mHandlerThread.start();
@@ -255,4 +276,87 @@ public class A2dpStateMachineTest {
        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
        Assert.assertThat(mA2dpStateMachine.getCurrentState(),
                IsInstanceOf.instanceOf(A2dpStateMachine.Disconnected.class));
                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);
    }
}
}