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

Commit 02c711c6 authored by My Name's avatar My Name
Browse files

State change tests for BassClientStateMachineTest

Bug: 237467631
Tag: #refactor
Test: BassClientStateMachineTest
Change-Id: I2a12720385b95092673dc950c88661013ed91dfc
parent 7132b71b
Loading
Loading
Loading
Loading
+104 −6
Original line number Original line Diff line number Diff line
@@ -58,6 +58,7 @@ package com.android.bluetooth.bass_client;


import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_CONNECT;


import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGatt;
@@ -106,6 +107,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Scanner;
import java.util.Scanner;
import java.util.UUID;
import java.util.stream.IntStream;
import java.util.stream.IntStream;


@VisibleForTesting
@VisibleForTesting
@@ -161,8 +163,8 @@ public class BassClientStateMachine extends StateMachine {
    private boolean mDiscoveryInitiated = false;
    private boolean mDiscoveryInitiated = false;
    @VisibleForTesting
    @VisibleForTesting
    BassClientService mService;
    BassClientService mService;

    @VisibleForTesting
    private BluetoothGattCharacteristic mBroadcastScanControlPoint;
    BluetoothGattCharacteristic mBroadcastScanControlPoint;
    private boolean mFirstTimeBisDiscovery = false;
    private boolean mFirstTimeBisDiscovery = false;
    private int mPASyncRetryCounter = 0;
    private int mPASyncRetryCounter = 0;
    private ScanResult mScanRes = null;
    private ScanResult mScanRes = null;
@@ -185,8 +187,8 @@ public class BassClientStateMachine extends StateMachine {
    private int mBroadcastSourceIdLength = 3;
    private int mBroadcastSourceIdLength = 3;
    private byte mNextSourceId = 0;
    private byte mNextSourceId = 0;
    private boolean mAllowReconnect = false;
    private boolean mAllowReconnect = false;

    @VisibleForTesting
    BluetoothGatt mBluetoothGatt = null;
    BluetoothGattTestableWrapper mBluetoothGatt = null;
    BluetoothGattCallback mGattCallback = null;
    BluetoothGattCallback mGattCallback = null;


    BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper,
    BassClientStateMachine(BluetoothDevice device, BassClientService svc, Looper looper,
@@ -1010,11 +1012,16 @@ public class BassClientStateMachine extends StateMachine {
            mGattCallback = new GattCallback();
            mGattCallback = new GattCallback();
        }
        }


        mBluetoothGatt = mDevice.connectGatt(mService, autoConnect,
        BluetoothGatt gatt = mDevice.connectGatt(mService, autoConnect,
                mGattCallback, BluetoothDevice.TRANSPORT_LE,
                mGattCallback, BluetoothDevice.TRANSPORT_LE,
                (BluetoothDevice.PHY_LE_1M_MASK
                (BluetoothDevice.PHY_LE_1M_MASK
                        | BluetoothDevice.PHY_LE_2M_MASK
                        | BluetoothDevice.PHY_LE_2M_MASK
                        | BluetoothDevice.PHY_LE_CODED_MASK), null);
                        | BluetoothDevice.PHY_LE_CODED_MASK), null);

        if (gatt != null) {
            mBluetoothGatt = new BluetoothGattTestableWrapper(gatt);
        }

        return mBluetoothGatt != null;
        return mBluetoothGatt != null;
    }
    }


@@ -1462,6 +1469,12 @@ public class BassClientStateMachine extends StateMachine {
            removeDeferredMessages(CONNECT);
            removeDeferredMessages(CONNECT);
            if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) {
            if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) {
                log("CONNECTED->CONNECTED: Ignore");
                log("CONNECTED->CONNECTED: Ignore");
                // Broadcast for testing purpose only
                if (Utils.isInstrumentationTestMode()) {
                    Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
                    mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
                            Utils.getTempAllowlistBroadcastOptions());
                }
            } else {
            } else {
                broadcastConnectionState(mDevice, mLastConnectionState,
                broadcastConnectionState(mDevice, mLastConnectionState,
                        BluetoothProfile.STATE_CONNECTED);
                        BluetoothProfile.STATE_CONNECTED);
@@ -1721,12 +1734,20 @@ public class BassClientStateMachine extends StateMachine {
        }
        }
    }
    }


    // public for testing, but private for non-testing
    @VisibleForTesting
    @VisibleForTesting
    class ConnectedProcessing extends State {
    class ConnectedProcessing extends State {
        @Override
        @Override
        public void enter() {
        public void enter() {
            log("Enter ConnectedProcessing(" + mDevice + "): "
            log("Enter ConnectedProcessing(" + mDevice + "): "
                    + messageWhatToString(getCurrentMessage().what));
                    + messageWhatToString(getCurrentMessage().what));

            // Broadcast for testing purpose only
            if (Utils.isInstrumentationTestMode()) {
                Intent intent = new Intent("android.bluetooth.bass_client.NOTIFY_TEST");
                mService.sendBroadcast(intent, BLUETOOTH_CONNECT,
                        Utils.getTempAllowlistBroadcastOptions());
            }
        }
        }
        @Override
        @Override
        public void exit() {
        public void exit() {
@@ -2020,4 +2041,81 @@ public class BassClientStateMachine extends StateMachine {
        }
        }
        Log.d(TAG, builder.toString());
        Log.d(TAG, builder.toString());
    }
    }

    /** Mockable wrapper of {@link BluetoothGatt}. */
    @VisibleForTesting
    public static class BluetoothGattTestableWrapper {
        public final BluetoothGatt mWrappedBluetoothGatt;

        BluetoothGattTestableWrapper(BluetoothGatt bluetoothGatt) {
            mWrappedBluetoothGatt = bluetoothGatt;
        }

        /** See {@link BluetoothGatt#getServices()}. */
        public List<BluetoothGattService> getServices() {
            return mWrappedBluetoothGatt.getServices();
        }

        /** See {@link BluetoothGatt#getService(UUID)}. */
        @Nullable
        public BluetoothGattService getService(UUID uuid) {
            return mWrappedBluetoothGatt.getService(uuid);
        }

        /** See {@link BluetoothGatt#discoverServices()}. */
        public boolean discoverServices() {
            return mWrappedBluetoothGatt.discoverServices();
        }

        /**
         * See {@link BluetoothGatt#readCharacteristic(
         * BluetoothGattCharacteristic)}.
         */
        public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
            return mWrappedBluetoothGatt.readCharacteristic(characteristic);
        }

        /**
         * See {@link BluetoothGatt#writeCharacteristic(
         * BluetoothGattCharacteristic, byte[], int)} .
         */
        public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
            return mWrappedBluetoothGatt.writeCharacteristic(characteristic);
        }

        /** See {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */
        public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
            return mWrappedBluetoothGatt.readDescriptor(descriptor);
        }

        /**
         * See {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor,
         * byte[])}.
         */
        public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
            return mWrappedBluetoothGatt.writeDescriptor(descriptor);
        }

        /** See {@link BluetoothGatt#requestMtu(int)}. */
        public boolean requestMtu(int mtu) {
            return mWrappedBluetoothGatt.requestMtu(mtu);
        }

        /** See {@link BluetoothGatt#setCharacteristicNotification}. */
        public boolean setCharacteristicNotification(
                BluetoothGattCharacteristic characteristic, boolean enable) {
            return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable);
        }

        /** See {@link BluetoothGatt#disconnect()}. */
        public void disconnect() {
            mWrappedBluetoothGatt.disconnect();
        }

        /** See {@link BluetoothGatt#close()}. */
        public void close() {
            mWrappedBluetoothGatt.close();
        }
    }

}
}
+147 −13
Original line number Original line Diff line number Diff line
@@ -21,34 +21,34 @@ import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.reset;


import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.os.Bundle;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.telecom.Log;


import androidx.test.filters.MediumTest;
import androidx.test.filters.MediumTest;


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


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

import org.hamcrest.core.IsInstanceOf;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.Test;
@@ -56,25 +56,29 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.junit.MockitoRule;


import java.util.UUID;

@MediumTest
@MediumTest
@RunWith(JUnit4.class)
@RunWith(JUnit4.class)
public class BassClientStateMachineTest {
public class BassClientStateMachineTest {
    @Rule
    @Rule
    public final MockitoRule mockito = MockitoJUnit.rule();
    public final MockitoRule mockito = MockitoJUnit.rule();


    private BluetoothAdapter mAdapter;
    private HandlerThread mHandlerThread;
    private StubBassClientStateMachine mBassClientStateMachine;
    private static final int CONNECTION_TIMEOUT_MS = 1_000;
    private static final int CONNECTION_TIMEOUT_MS = 1_000;
    private static final int TIMEOUT_MS = 2_000;
    private static final int TIMEOUT_MS = 2_000;
    private static final int WAIT_MS = 1_200;
    private static final int WAIT_MS = 1_200;

    private BluetoothAdapter mAdapter;
    private HandlerThread mHandlerThread;
    private StubBassClientStateMachine mBassClientStateMachine;
    private BluetoothDevice mTestDevice;
    private BluetoothDevice mTestDevice;
    @Mock private AdapterService mAdapterService;
    @Mock
    @Mock private BassClientService mBassClientService;
    private AdapterService mAdapterService;
    @Mock
    private BassClientService mBassClientService;


    @Before
    @Before
    public void setUp() throws Exception {
    public void setUp() throws Exception {
@@ -95,6 +99,7 @@ public class BassClientStateMachineTest {


    @After
    @After
    public void tearDown() throws Exception {
    public void tearDown() throws Exception {
        Log.d("hieu-debug", "blo blo");
        mBassClientStateMachine.doQuit();
        mBassClientStateMachine.doQuit();
        mHandlerThread.quit();
        mHandlerThread.quit();
        TestUtils.clearAdapterService(mAdapterService);
        TestUtils.clearAdapterService(mAdapterService);
@@ -221,6 +226,131 @@ public class BassClientStateMachineTest {
                IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class));
                IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class));
    }
    }


    @Test
    public void testStatesChangesWithMessages() {
        allowConnection(true);
        allowConnectGatt(true);

        assertThat(mBassClientStateMachine.getCurrentState())
                .isInstanceOf(BassClientStateMachine.Disconnected.class);

        // disconnected -> connecting ---timeout---> disconnected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT),
                BassClientStateMachine.Connecting.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT_TIMEOUT),
                BassClientStateMachine.Disconnected.class);

        // disconnected -> connecting ---DISCONNECT---> disconnected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT),
                BassClientStateMachine.Connecting.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.DISCONNECT),
                BassClientStateMachine.Disconnected.class);

        // disconnected -> connecting ---CONNECTION_STATE_CHANGED(connected)---> connected -->
        // disconnected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT),
                BassClientStateMachine.Connecting.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.CONNECTION_STATE_CHANGED,
                        Integer.valueOf(BluetoothProfile.STATE_CONNECTED)),
                BassClientStateMachine.Connected.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.CONNECTION_STATE_CHANGED,
                        Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)),
                BassClientStateMachine.Disconnected.class);

        // disconnected -> connecting ---CONNECTION_STATE_CHANGED(non-connected) --> disconnected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT),
                BassClientStateMachine.Connecting.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.CONNECTION_STATE_CHANGED,
                        Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)),
                BassClientStateMachine.Disconnected.class);

        // change default state to connected for the next tests
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT),
                BassClientStateMachine.Connecting.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.CONNECTION_STATE_CHANGED,
                        Integer.valueOf(BluetoothProfile.STATE_CONNECTED)),
                BassClientStateMachine.Connected.class);

        // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED
        // --> connected


        // Make bluetoothGatt non-null so state will transit
        mBassClientStateMachine.mBluetoothGatt = Mockito.mock(
                BassClientStateMachine.BluetoothGattTestableWrapper.class);
        mBassClientStateMachine.mBroadcastScanControlPoint = new BluetoothGattCharacteristic(
                BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT,
                BluetoothGattCharacteristic.PROPERTY_READ,
                BluetoothGattCharacteristic.PERMISSION_READ);

        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.READ_BASS_CHARACTERISTICS,
                        new BluetoothGattCharacteristic(UUID.randomUUID(),
                                BluetoothGattCharacteristic.PROPERTY_READ,
                                BluetoothGattCharacteristic.PERMISSION_READ)),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);

        // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT -->
        // connected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(
                        BassClientStateMachine.READ_BASS_CHARACTERISTICS,
                        new BluetoothGattCharacteristic(UUID.randomUUID(),
                                BluetoothGattCharacteristic.PROPERTY_READ,
                                BluetoothGattCharacteristic.PERMISSION_READ)),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT),
                BassClientStateMachine.Connected.class);

        // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED-->
        // connected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);

        // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);
    }

    private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
        Mockito.clearInvocations(mBassClientService);
        mBassClientStateMachine.sendMessage(msg);
        // Verify that one connection state broadcast is executed
        verify(mBassClientService, timeout(TIMEOUT_MS)
                .times(1))
                .sendBroadcast(any(Intent.class), anyString(), any());
        Assert.assertThat(mBassClientStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type));
    }


    // It simulates GATT connection for testing.
    // It simulates GATT connection for testing.
    public static class StubBassClientStateMachine extends BassClientStateMachine {
    public static class StubBassClientStateMachine extends BassClientStateMachine {
        boolean mShouldAllowGatt = true;
        boolean mShouldAllowGatt = true;
@@ -238,7 +368,11 @@ public class BassClientStateMachineTest {


        public void notifyConnectionStateChanged(int status, int newState) {
        public void notifyConnectionStateChanged(int status, int newState) {
            if (mGattCallback != null) {
            if (mGattCallback != null) {
                mGattCallback.onConnectionStateChange(mBluetoothGatt, status, newState);
                BluetoothGatt gatt = null;
                if (mBluetoothGatt != null) {
                    gatt = mBluetoothGatt.mWrappedBluetoothGatt;
                }
                mGattCallback.onConnectionStateChange(gatt, status, newState);
            }
            }
        }
        }
    }
    }