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

Commit cbfeefdc authored by Hieu Dang's avatar Hieu Dang Committed by Gerrit Code Review
Browse files

Merge "State change tests for BassClientStateMachineTest"

parents c7d66559 02c711c6
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,
@@ -1011,11 +1013,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;
    }
    }


@@ -1465,6 +1472,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);
@@ -1724,12 +1737,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() {
@@ -2023,4 +2044,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);
            }
            }
        }
        }
    }
    }