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

Commit 21a51725 authored by Sal Savage's avatar Sal Savage Committed by Gerrit Code Review
Browse files

Merge "Add unit tests for A2dpSinkStateMachine"

parents 624d60cb 8e6866e2
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -353,7 +353,15 @@ public class A2dpSinkService extends ProfileService {
        return true;
    }

    void removeStateMachine(A2dpSinkStateMachine stateMachine) {
    /**
     * Remove a device's state machine.
     *
     * Called by the state machines when they disconnect.
     *
     * Visible for testing so it can be mocked and verified on.
     */
    @VisibleForTesting
    public void removeStateMachine(A2dpSinkStateMachine stateMachine) {
        mDeviceStateMap.remove(stateMachine.getDevice());
    }

+0 −4
Original line number Diff line number Diff line
@@ -86,10 +86,6 @@ public class A2dpSinkStateMachine extends StateMachine {
        setInitialState(mDisconnected);
    }

    protected String getConnectionStateChangedIntent() {
        return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
    }

    /**
     * Get the current connection state
     *
+370 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.bluetooth.a2dpsink;

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

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

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.runner.AndroidJUnit4;

import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
public class A2dpSinkStateMachineTest {
    private Context mTargetContext;

    private BluetoothAdapter mAdapter;
    private BluetoothDevice mDevice;
    private final String mDeviceAddress = "11:11:11:11:11:11";
    @Mock private A2dpSinkService mService;
    @Mock private A2dpSinkNativeInterface mNativeInterface;

    A2dpSinkStateMachine mStateMachine;
    private static final int TIMEOUT_MS = 1000;
    private static final int CONNECT_TIMEOUT_MS = 6000;
    private static final int UNHANDLED_MESSAGE = 9999;

    @Before
    public void setUp() throws Exception {
        mTargetContext = InstrumentationRegistry.getTargetContext();
        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();
        mDevice = mAdapter.getRemoteDevice(mDeviceAddress);

        doNothing().when(mService).removeStateMachine(any(A2dpSinkStateMachine.class));

        mStateMachine = new A2dpSinkStateMachine(mDevice, mService, mNativeInterface);
        mStateMachine.start();
        assertThat(mStateMachine.getDevice()).isEqualTo(mDevice);
        assertThat(mStateMachine.getAudioConfig()).isNull();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @After
    public void tearDown() throws Exception {
        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink)) {
            return;
        }
        mStateMachine = null;
        mDevice = null;
        mAdapter = null;
    }

    private void mockDeviceConnectionPolicy(BluetoothDevice device, int policy) {
        doReturn(policy).when(mService).getConnectionPolicy(device);
    }

    private void sendConnectionEvent(int state) {
        mStateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT,
                StackEvent.connectionStateChanged(mDevice, state));
    }

    private void sendAudioConfigChangedEvent(int sampleRate, int channelCount) {
        mStateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT,
                StackEvent.audioConfigChanged(mDevice, sampleRate, channelCount));
    }

    /**********************************************************************************************
     * DISCONNECTED STATE TESTS                                                                   *
     *********************************************************************************************/

    @Test
    public void testConnectInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        mStateMachine.connect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mNativeInterface, timeout(TIMEOUT_MS).times(1)).connectA2dpSink(mDevice);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
    }

    @Test
    public void testDisconnectInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        mStateMachine.disconnect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testAudioConfigChangedInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendAudioConfigChangedEvent(44, 1);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        assertThat(mStateMachine.getAudioConfig()).isNull();
    }

    @Test
    public void testIncomingConnectedInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testAllowedIncomingConnectionInDisconnected() {
        mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        verify(mNativeInterface, times(0)).connectA2dpSink(mDevice);
    }

    @Test
    public void testForbiddenIncomingConnectionInDisconnected() {
        mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mNativeInterface, times(1)).disconnectA2dpSink(mDevice);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testUnknownIncomingConnectionInDisconnected() {
        mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        verify(mNativeInterface, times(0)).connectA2dpSink(mDevice);
    }

    @Test
    public void testIncomingDisconnectInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
    }

    @Test
    public void testIncomingDisconnectingInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        verify(mService, times(0)).removeStateMachine(mStateMachine);
    }

    @Test
    public void testIncomingConnectingInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testUnhandledMessageInDisconnected() {
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        mStateMachine.sendMessage(UNHANDLED_MESSAGE);
        mStateMachine.sendMessage(UNHANDLED_MESSAGE, 0 /* arbitrary payload */);
    }

    /**********************************************************************************************
     * CONNECTING STATE TESTS                                                                     *
     *********************************************************************************************/

    @Test
    public void testConnectedInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testConnectingInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
    }

    @Test
    public void testDisconnectingInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
    }

    @Test
    public void testDisconnectedInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testConnectionTimeoutInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        verify(mService, timeout(CONNECT_TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testAudioStateChangeInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        sendAudioConfigChangedEvent(44, 1);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        assertThat(mStateMachine.getAudioConfig()).isNull();
    }

    @Test
    public void testConnectInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        mStateMachine.connect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
    }

    @Test
    public void testDisconnectInConnecting() {
        testConnectInDisconnected();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
        mStateMachine.disconnect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
    }

    /**********************************************************************************************
     * CONNECTED STATE TESTS                                                                      *
     *********************************************************************************************/

    @Test
    public void testConnectInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        mStateMachine.connect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testDisconnectInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        mStateMachine.disconnect();
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mNativeInterface, times(1)).disconnectA2dpSink(mDevice);
        verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testAudioStateChangeInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        sendAudioConfigChangedEvent(44, 1);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        BluetoothAudioConfig expected =
                new BluetoothAudioConfig(44, 1, AudioFormat.ENCODING_PCM_16BIT);
        BluetoothAudioConfig config = mStateMachine.getAudioConfig();
        assertThat(config).isEqualTo(expected);
    }

    @Test
    public void testConnectedInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testConnectingInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
    }

    @Test
    public void testDisconnectingInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    @Test
    public void testDisconnectedInConnected() {
        testConnectedInConnecting();
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
        sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
        TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
        verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
        assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    /**********************************************************************************************
     * OTHER TESTS                                                                                *
     *********************************************************************************************/

    @Test
    public void testDump() {
        StringBuilder sb = new StringBuilder();
        mStateMachine.dump(sb);
        assertThat(sb.toString()).isNotNull();
    }
}