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

Commit 9fd5c1f9 authored by Sal Savage's avatar Sal Savage
Browse files

Add unit tests for A2dpSinkService

Tag: #stability
Bug: 190403820
Test: atest BluetoothInstrumentationTests
Change-Id: Idb699c44a5a15831267e28927d53041bb931a4d6
parent 0fb8f7d5
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ public class A2dpSinkService extends ProfileService {

    private AdapterService mAdapterService;
    private DatabaseManager mDatabaseManager;
    protected Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
    private Map<BluetoothDevice, A2dpSinkStateMachine> mDeviceStateMap =
            new ConcurrentHashMap<>(1);

    private final Object mStreamHandlerLock = new Object();
@@ -333,6 +333,10 @@ public class A2dpSinkService extends ProfileService {
                    + ", InstanceMap start state: " + sb.toString());
        }

        if (device == null) {
            throw new IllegalArgumentException("Null device");
        }

        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
        // a state machine instance doesn't exist. maybe it is already gone?
        if (stateMachine == null) {
@@ -371,6 +375,11 @@ public class A2dpSinkService extends ProfileService {
        return existingStateMachine;
    }

    @VisibleForTesting
    protected A2dpSinkStateMachine getStateMachineForDevice(BluetoothDevice device) {
        return mDeviceStateMap.get(device);
    }

    List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
        List<BluetoothDevice> deviceList = new ArrayList<>();
@@ -400,6 +409,7 @@ public class A2dpSinkService extends ProfileService {
     * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
     */
    public int getConnectionState(BluetoothDevice device) {
        if (device == null) return BluetoothProfile.STATE_DISCONNECTED;
        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
        return (stateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
                : stateMachine.getState();
@@ -469,6 +479,7 @@ public class A2dpSinkService extends ProfileService {
    }

    BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
        if (device == null) return null;
        A2dpSinkStateMachine stateMachine = mDeviceStateMap.get(device);
        // a state machine instance doesn't exist. maybe it is already gone?
        if (stateMachine == null) {
+347 −34
Original line number Diff line number Diff line
@@ -15,12 +15,17 @@
 */
package com.android.bluetooth.a2dpsink;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.*;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAudioConfig;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioFormat;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -33,7 +38,6 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;

import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
@@ -42,6 +46,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class A2dpSinkServiceTest {
@@ -53,6 +60,17 @@ public class A2dpSinkServiceTest {

    @Mock private AdapterService mAdapterService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private A2dpSinkNativeInterface mNativeInterface;

    private BluetoothDevice mDevice1;
    private BluetoothDevice mDevice2;
    private BluetoothDevice mDevice3;
    private BluetoothDevice mDevice4;
    private BluetoothDevice mDevice5;
    private BluetoothDevice mDevice6;

    private static final int TEST_SAMPLE_RATE = 44;
    private static final int TEST_CHANNEL_COUNT = 1;

    @Before
    public void setUp() throws Exception {
@@ -60,16 +78,33 @@ public class A2dpSinkServiceTest {
        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
                mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
        MockitoAnnotations.initMocks(this);

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        assertThat(mAdapter).isNotNull();
        mDevice1 = makeBluetoothDevice("11:11:11:11:11:11");
        mDevice2 = makeBluetoothDevice("22:22:22:22:22:22");
        mDevice3 = makeBluetoothDevice("33:33:33:33:33:33");
        mDevice4 = makeBluetoothDevice("44:44:44:44:44:44");
        mDevice5 = makeBluetoothDevice("55:55:55:55:55:55");
        mDevice6 = makeBluetoothDevice("66:66:66:66:66:66");
        BluetoothDevice[] bondedDevices = new BluetoothDevice[]{
            mDevice1, mDevice2, mDevice3, mDevice4, mDevice5, mDevice6
        };

        // Setup the adapter service and start our service under test
        TestUtils.setAdapterService(mAdapterService);
        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        doReturn(bondedDevices).when(mAdapterService).getBondedDevices();
        when(mDatabaseManager.setProfileConnectionPolicy(any(), anyInt(),
                anyInt())).thenReturn(true);
        setMaxConnectedAudioDevices(1);
        TestUtils.startService(mServiceRule, A2dpSinkService.class);
        mService = A2dpSinkService.getA2dpSinkService();
        Assert.assertNotNull(mService);
        // Try getting the Bluetooth adapter
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        Assert.assertNotNull(mAdapter);
        assertThat(mService).isNotNull();

        mService.mNativeInterface = mNativeInterface;
        doReturn(true).when(mNativeInterface).setActiveDevice(any());
    }

    @After
@@ -79,10 +114,31 @@ public class A2dpSinkServiceTest {
        }
        TestUtils.stopService(mServiceRule, A2dpSinkService.class);
        mService = A2dpSinkService.getA2dpSinkService();
        Assert.assertNull(mService);
        assertThat(mService).isNull();
        TestUtils.clearAdapterService(mAdapterService);
    }

    private void setupDeviceConnection(BluetoothDevice device) {
        assertThat(mService.getConnectionState(device)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mService.connect(device)).isTrue();
        sendConnectionEvent(device, StackEvent.CONNECTION_STATE_CONNECTED);
        waitForDeviceProcessing(device);
        assertThat(mService.getConnectionState(device)).isEqualTo(
                BluetoothProfile.STATE_CONNECTED);
    }

    private void sendConnectionEvent(BluetoothDevice device, int newState) {
        StackEvent event = StackEvent.connectionStateChanged(device, newState);
        mService.messageFromNative(event);
    }

    private void waitForDeviceProcessing(BluetoothDevice device) {
        A2dpSinkStateMachine sm = mService.getStateMachineForDevice(device);
        if (sm == null) return;
        TestUtils.waitForLooperToFinishScheduledTask(sm.getHandler().getLooper());
    }

    private BluetoothDevice makeBluetoothDevice(String address) {
        return mAdapter.getRemoteDevice(address);
    }
@@ -105,29 +161,49 @@ public class A2dpSinkServiceTest {
                .thenReturn(priority);
    }

    /**
     * Test that initialization of the service completes and that we can get a instance
     */
    @Test
    public void testInitialize() {
        Assert.assertNotNull(A2dpSinkService.getA2dpSinkService());
        assertThat(A2dpSinkService.getA2dpSinkService()).isNotNull();
    }

    /**
     * Test that a PRIORITY_ON device is connected to
     * Test that asking to connect with a null device fails
     */
    @Test
    public void testConnect() {
        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        Assert.assertTrue(mService.connect(device));
    public void testConnectNullDevice() {
        assertThrows(IllegalArgumentException.class, () -> mService.connect(null));
    }

    /**
     * Test that a PRIORITY_OFF device is not connected to
     * Test that a CONNECTION_POLICY_ALLOWED device can connected
     */
    @Test
    public void testConnectPriorityOffDevice() {
        BluetoothDevice device = makeBluetoothDevice("11:11:11:11:11:11");
        mockDevicePriority(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        Assert.assertFalse(mService.connect(device));
    public void testConnectPolicyAllowedDevice() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);
    }

    /**
     * Test that a CONNECTION_POLICY_FORBIDDEN device is not allowed to connect
     */
    @Test
    public void testConnectPolicyForbiddenDevice() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        assertThat(mService.connect(mDevice1)).isFalse();
        assertThat(mService.getConnectionState(mDevice1)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);
    }

    /**
     * Test that a CONNECTION_POLICY_UNKNOWN device is allowed to connect
     */
    @Test
    public void testConnectPolicyUnknownDevice() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        setupDeviceConnection(mDevice1);
    }

    /**
@@ -137,22 +213,259 @@ public class A2dpSinkServiceTest {
    public void testConnectMultipleDevices() {
        setMaxConnectedAudioDevices(5);

        BluetoothDevice device1 = makeBluetoothDevice("11:11:11:11:11:11");
        BluetoothDevice device2 = makeBluetoothDevice("22:22:22:22:22:22");
        BluetoothDevice device3 = makeBluetoothDevice("33:33:33:33:33:33");
        BluetoothDevice device4 = makeBluetoothDevice("44:44:44:44:44:44");
        BluetoothDevice device5 = makeBluetoothDevice("55:55:55:55:55:55");

        mockDevicePriority(device1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(device2, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(device3, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(device4, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(device5, BluetoothProfile.CONNECTION_POLICY_ALLOWED);

        Assert.assertTrue(mService.connect(device1));
        Assert.assertTrue(mService.connect(device2));
        Assert.assertTrue(mService.connect(device3));
        Assert.assertTrue(mService.connect(device4));
        Assert.assertTrue(mService.connect(device5));
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice2, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice3, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice4, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice5, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        mockDevicePriority(mDevice6, BluetoothProfile.CONNECTION_POLICY_ALLOWED);

        setupDeviceConnection(mDevice1);
        setupDeviceConnection(mDevice2);
        setupDeviceConnection(mDevice3);
        setupDeviceConnection(mDevice4);
        setupDeviceConnection(mDevice5);
    }

    /**
     * Test to make sure we can disconnect a connected device
     */
    @Test
    public void testDisconnect() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);

        assertThat(mService.disconnect(mDevice1)).isTrue();
        waitForDeviceProcessing(mDevice1);
        assertThat(mService.getConnectionState(mDevice1)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);
    }

    /**
     * Assure disconnect() fails with a device that's not connected
     */
    @Test
    public void testDisconnectDeviceDoesNotExist() {
        assertThat(mService.disconnect(mDevice1)).isFalse();
    }

    /**
     * Assure disconnect() fails with an invalid device
     */
    @Test
    public void testDisconnectNullDevice() {
        assertThrows(IllegalArgumentException.class, () -> mService.disconnect(null));
    }

    /**
     * Assure dump() returns something and does not crash
     */
    @Test
    public void testDump() {
        StringBuilder sb = new StringBuilder();
        mService.dump(sb);
        assertThat(sb.toString()).isNotNull();
    }

    /**
     * Test that we can set the active device to a valid device and receive it back from
     * GetActiveDevice()
     */
    @Test
    public void testSetActiveDevice() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        assertThat(mService.getActiveDevice()).isNotEqualTo(mDevice1);
        assertThat(mService.setActiveDevice(mDevice1)).isTrue();
        assertThat(mService.getActiveDevice()).isEqualTo(mDevice1);
    }

    /**
     * Test that calls to set a null active device succeed in unsetting the active device
     */
    @Test
    public void testSetActiveDeviceNullDevice() {
        assertThat(mService.setActiveDevice(null)).isTrue();
        assertThat(mService.getActiveDevice()).isNull();
    }

    /**
     * Make sure we can receive the set audio configuration
     */
    @Test
    public void testGetAudioConfiguration() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);

        StackEvent audioConfigChanged =
                StackEvent.audioConfigChanged(mDevice1, TEST_SAMPLE_RATE, TEST_CHANNEL_COUNT);
        mService.messageFromNative(audioConfigChanged);
        waitForDeviceProcessing(mDevice1);

        BluetoothAudioConfig expected = new BluetoothAudioConfig(TEST_SAMPLE_RATE,
                TEST_CHANNEL_COUNT, AudioFormat.ENCODING_PCM_16BIT);
        BluetoothAudioConfig config = mService.getAudioConfig(mDevice1);
        assertThat(config).isEqualTo(expected);
    }

    /**
     * Getting an audio config for a device that hasn't received one yet should return null
     */
    @Test
    public void testGetAudioConfigWithConfigUnset() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);
        assertThat(mService.getAudioConfig(mDevice1)).isNull();
    }

    /**
     * Getting an audio config for a null device should return null
     */
    @Test
    public void testGetAudioConfigNullDevice() {
        assertThat(mService.getAudioConfig(null)).isNull();
    }

    /**
     * Test that a newly connected device ends up in the set returned by
     * getConnectedDevices
     */
    @Test
    public void testGetConnectedDevices() {
        ArrayList<BluetoothDevice> expected = new ArrayList<BluetoothDevice>();
        expected.add(mDevice1);

        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);

        List<BluetoothDevice> devices = mService.getConnectedDevices();
        assertThat(devices).isEqualTo(expected);
    }

    /**
     * Test that a newly connected device ends up in the set returned by
     * testGetDevicesMatchingConnectionStates
     */
    @Test
    public void testGetDevicesMatchingConnectionStatesConnected() {
        ArrayList<BluetoothDevice> expected = new ArrayList<BluetoothDevice>();
        expected.add(mDevice1);
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);

        List<BluetoothDevice> devices = mService.getDevicesMatchingConnectionStates(
                new int[] {BluetoothProfile.STATE_CONNECTED});
        assertThat(devices).isEqualTo(expected);
    }

    /**
     * Test that a all bonded device end up in the set returned by
     * testGetDevicesMatchingConnectionStates, even when they're disconnected
     */
    @Test
    public void testGetDevicesMatchingConnectionStatesDisconnected() {
        ArrayList<BluetoothDevice> expected = new ArrayList<BluetoothDevice>();
        expected.add(mDevice1);
        expected.add(mDevice2);
        expected.add(mDevice3);
        expected.add(mDevice4);
        expected.add(mDevice5);
        expected.add(mDevice6);

        List<BluetoothDevice> devices = mService.getDevicesMatchingConnectionStates(
                new int[] {BluetoothProfile.STATE_DISCONNECTED});
        assertThat(devices).isEqualTo(expected);
    }

    /**
     * Test that GetConnectionPolicy() can get a device with policy "Allowed"
     */
    @Test
    public void testGetConnectionPolicyDeviceAllowed() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        assertThat(mService.getConnectionPolicy(mDevice1)).isEqualTo(
                BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    /**
     * Test that GetConnectionPolicy() can get a device with policy "Forbidden"
     */
    @Test
    public void testGetConnectionPolicyDeviceForbidden() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        assertThat(mService.getConnectionPolicy(mDevice1)).isEqualTo(
                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
    }

    /**
     * Test that GetConnectionPolicy() can get a device with policy "Unknown"
     */
    @Test
    public void testGetConnectionPolicyDeviceUnknown() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        assertThat(mService.getConnectionPolicy(mDevice1)).isEqualTo(
                BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
    }

    /**
     * Test that SetConnectionPolicy() can change a device's policy to "Allowed"
     */
    @Test
    public void testSetConnectionPolicyDeviceAllowed() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
    }

    /**
     * Test that SetConnectionPolicy() can change a device's policy to "Forbidden"
     */
    @Test
    public void testSetConnectionPolicyDeviceForbiddenWhileNotConnected() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
    }

    /**
     * Test that SetConnectionPolicy() can change a connected device's policy to "Forbidden"
     * and that the new "Forbidden" policy causes a disconnect of the device.
     */
    @Test
    public void testSetConnectionPolicyDeviceForbiddenWhileConnected() {
        mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        setupDeviceConnection(mDevice1);

        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);

        waitForDeviceProcessing(mDevice1);
        assertThat(mService.getConnectionState(mDevice1)).isEqualTo(
                BluetoothProfile.STATE_DISCONNECTED);
    }

    /**
     * Test that SetConnectionPolicy() can change a device's policy to "Unknown"
     */
    @Test
    public void testSetConnectionPolicyDeviceUnknown() {
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_UNKNOWN)).isTrue();
        verify(mDatabaseManager, times(1)).setProfileConnectionPolicy(mDevice1,
                BluetoothProfile.A2DP_SINK, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
    }

    /**
     * Test that SetConnectionPolicy is robust to DatabaseManager failures
     */
    @Test
    public void testSetConnectionPolicyDatabaseWriteFails() {
        when(mDatabaseManager.setProfileConnectionPolicy(any(), anyInt(),
                anyInt())).thenReturn(false);
        assertThat(mService.setConnectionPolicy(mDevice1,
                BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isFalse();
    }
}