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

Commit 1c06088c authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Add tests for BassClientStateMachine#ConnectedProcessing

And remove the unrechable Disconnecting state in
BassClientStateMachine,and the wrong documentations of
BassClientStateMachine.

Bug: 237467631
Test: atest BluetoothInstrumentationTests:BassClientStateMachineTest
Change-Id: Ifc92b1b01a65248298b3cd84d738b4abdc3250e9
Merged-In: Ifc92b1b01a65248298b3cd84d738b4abdc3250e9
(cherry picked from commit 176a9769)
parent 873a58c6
Loading
Loading
Loading
Loading
+7 −110
Original line number Diff line number Diff line
@@ -14,46 +14,6 @@
 * limitations under the License.
 */

/**
 * Bluetooth Bassclient StateMachine. There is one instance per remote device.
 *  - "Disconnected" and "Connected" are steady states.
 *  - "Connecting" and "Disconnecting" are transient states until the
 *     connection / disconnection is completed.
 *  - "ConnectedProcessing" is an intermediate state to ensure, there is only
 *    one Gatt transaction from the profile at any point of time
 *
 *
 *                        (Disconnected)
 *                           |       ^
 *                   CONNECT |       | DISCONNECTED
 *                           V       |
 *                 (Connecting)<--->(Disconnecting)
 *                           |       ^
 *                 CONNECTED |       | DISCONNECT
 *                           V       |
 *                          (Connected)
 *                           |       ^
 *                 GATT_TXN  |       | GATT_TXN_DONE/GATT_TXN_TIMEOUT
 *                           V       |
 *                          (ConnectedProcessing)
 * NOTES:
 *  - If state machine is in "Connecting" state and the remote device sends
 *    DISCONNECT request, the state machine transitions to "Disconnecting" state.
 *  - Similarly, if the state machine is in "Disconnecting" state and the remote device
 *    sends CONNECT request, the state machine transitions to "Connecting" state.
 *  - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and
 *    all other requests (add, update, remove source) operations will be deferred in
 *    "ConnectedProcessing" state
 *  - Once the gatt transaction is done (or after a specified timeout of no response),
 *    State machine will move back to "Connected" and try to process the deferred requests
 *    as needed
 *
 *                    DISCONNECT
 *    (Connecting) ---------------> (Disconnecting)
 *                 <---------------
 *                      CONNECT
 *
 */
package com.android.bluetooth.bass_client;

import static android.Manifest.permission.BLUETOOTH_CONNECT;
@@ -153,7 +113,6 @@ public class BassClientStateMachine extends StateMachine {
    private final Disconnected mDisconnected = new Disconnected();
    private final Connected mConnected = new Connected();
    private final Connecting mConnecting = new Connecting();
    private final Disconnecting mDisconnecting = new Disconnecting();
    private final ConnectedProcessing mConnectedProcessing = new ConnectedProcessing();
    @VisibleForTesting
    final List<BluetoothGattCharacteristic> mBroadcastCharacteristics =
@@ -176,8 +135,10 @@ public class BassClientStateMachine extends StateMachine {
    private BluetoothAdapter mBluetoothAdapter =
            BluetoothAdapter.getDefaultAdapter();
    private ServiceFactory mFactory = new ServiceFactory();
    private int mPendingOperation = -1;
    private byte mPendingSourceId = -1;
    @VisibleForTesting
    int mPendingOperation = -1;
    @VisibleForTesting
    byte mPendingSourceId = -1;
    private BluetoothLeBroadcastMetadata mPendingMetadata = null;
    private BluetoothLeBroadcastReceiveState mSetBroadcastPINRcvState = null;
    private boolean mSetBroadcastCodePending = false;
@@ -185,7 +146,8 @@ public class BassClientStateMachine extends StateMachine {
    // Psync and PAST interfaces
    private PeriodicAdvertisingManager mPeriodicAdvManager;
    private boolean mAutoAssist = false;
    private boolean mAutoTriggered = false;
    @VisibleForTesting
    boolean mAutoTriggered = false;
    @VisibleForTesting
    boolean mNoStopScanOffload = false;
    private boolean mDefNoPAS = false;
@@ -204,7 +166,6 @@ public class BassClientStateMachine extends StateMachine {
        mService = svc;
        mConnectTimeoutMs = connectTimeoutMs;
        addState(mDisconnected);
        addState(mDisconnecting);
        addState(mConnected);
        addState(mConnecting);
        addState(mConnectedProcessing);
@@ -1804,7 +1765,7 @@ public class BassClientStateMachine extends StateMachine {
                    transitionTo(mConnected);
                    break;
                case GATT_TXN_TIMEOUT:
                    log("GATT transaction timedout for" + mDevice);
                    log("GATT transaction timeout for" + mDevice);
                    sendPendingCallbacks(
                            mPendingOperation,
                            BluetoothStatusCodes.ERROR_UNKNOWN);
@@ -1830,67 +1791,6 @@ public class BassClientStateMachine extends StateMachine {
        }
    }

    @VisibleForTesting
    class Disconnecting extends State {
        @Override
        public void enter() {
            log("Enter Disconnecting(" + mDevice + "): "
                    + messageWhatToString(getCurrentMessage().what));
            sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs);
            broadcastConnectionState(
                    mDevice, mLastConnectionState, BluetoothProfile.STATE_DISCONNECTING);
        }

        @Override
        public void exit() {
            log("Exit Disconnecting(" + mDevice + "): "
                    + messageWhatToString(getCurrentMessage().what));
            removeMessages(CONNECT_TIMEOUT);
            mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING;
        }

        @Override
        public boolean processMessage(Message message) {
            log("Disconnecting process message(" + mDevice + "): "
                    + messageWhatToString(message.what));
            switch (message.what) {
                case CONNECT:
                    log("Disconnecting to " + mDevice);
                    log("deferring this connection request " + mDevice);
                    deferMessage(message);
                    break;
                case DISCONNECT:
                    Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice);
                    break;
                case CONNECTION_STATE_CHANGED:
                    int state = (int) message.obj;
                    Log.w(TAG, "Disconnecting: connection state changed:" + state);
                    if (state == BluetoothProfile.STATE_CONNECTED) {
                        Log.e(TAG, "should never happen from this state");
                        transitionTo(mConnected);
                    } else {
                        Log.w(TAG, "disconnection successful to " + mDevice);
                        cancelActiveSync(null);
                        transitionTo(mDisconnected);
                    }
                    break;
                case CONNECT_TIMEOUT:
                    Log.w(TAG, "CONNECT_TIMEOUT");
                    BluetoothDevice device = (BluetoothDevice) message.obj;
                    if (!mDevice.equals(device)) {
                        Log.e(TAG, "Unknown device timeout " + device);
                        break;
                    }
                    transitionTo(mDisconnected);
                    break;
                default:
                    log("Disconnecting: not handled message:" + message.what);
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) {
        log("broadcastConnectionState " + device + ": " + fromState + "->" + toState);
        if (fromState == BluetoothProfile.STATE_CONNECTED
@@ -1917,9 +1817,6 @@ public class BassClientStateMachine extends StateMachine {
            case "Disconnected":
                log("Disconnected");
                return BluetoothProfile.STATE_DISCONNECTED;
            case "Disconnecting":
                log("Disconnecting");
                return BluetoothProfile.STATE_DISCONNECTING;
            case "Connecting":
                log("Connecting");
                return BluetoothProfile.STATE_CONNECTING;
+223 −6
Original line number Diff line number Diff line
@@ -16,18 +16,26 @@

package com.android.bluetooth.bass_client;

import static android.bluetooth.BluetoothGatt.GATT_FAILURE;
import static android.bluetooth.BluetoothGatt.GATT_SUCCESS;

import static com.android.bluetooth.bass_client.BassClientStateMachine.ADD_BCAST_SOURCE;
import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECTION_STATE_CHANGED;
import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_TIMEOUT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED;
import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS;
import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_START;
import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOTE_SCAN_STOP;
import static com.android.bluetooth.bass_client.BassClientStateMachine.REMOVE_BCAST_SOURCE;
import static com.android.bluetooth.bass_client.BassClientStateMachine.SELECT_BCAST_SOURCE;
import static com.android.bluetooth.bass_client.BassClientStateMachine.SET_BCAST_CODE;
import static com.android.bluetooth.bass_client.BassClientStateMachine.START_SCAN_OFFLOAD;
import static com.android.bluetooth.bass_client.BassClientStateMachine.STOP_SCAN_OFFLOAD;
import static com.android.bluetooth.bass_client.BassClientStateMachine.UPDATE_BCAST_SOURCE;

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

@@ -313,7 +321,6 @@ public class BassClientStateMachineTest {
        // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_PROCESSED
        // --> connected


        // Make bluetoothGatt non-null so state will transit
        mBassClientStateMachine.mBluetoothGatt = Mockito.mock(
                BassClientStateMachine.BluetoothGattTestableWrapper.class);
@@ -330,7 +337,7 @@ public class BassClientStateMachineTest {
                                BluetoothGattCharacteristic.PERMISSION_READ)),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);

        // connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT -->
@@ -343,7 +350,7 @@ public class BassClientStateMachineTest {
                                BluetoothGattCharacteristic.PERMISSION_READ)),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_TIMEOUT),
                mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT),
                BassClientStateMachine.Connected.class);

        // connected ----START_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED-->
@@ -352,7 +359,7 @@ public class BassClientStateMachineTest {
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.START_SCAN_OFFLOAD),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);

        // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected
@@ -360,7 +367,7 @@ public class BassClientStateMachineTest {
                mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD),
                BassClientStateMachine.ConnectedProcessing.class);
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED),
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED),
                BassClientStateMachine.Connected.class);
    }

@@ -491,7 +498,8 @@ public class BassClientStateMachineTest {
        mBassClientStateMachine.mBluetoothGatt = btGatt;

        mBassClientStateMachine.sendMessage(DISCONNECT);
        verify(btGatt, timeout(TIMEOUT_MS)).disconnect();
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(btGatt).disconnect();
        verify(btGatt).close();
    }

@@ -733,6 +741,198 @@ public class BassClientStateMachineTest {

    // TODO: add sendMessage tests for BCAST related messages

    @Test
    public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();

        mBassClientStateMachine.sendMessage(CONNECT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
    }

    @Test
    public void sendDisconnectMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();

        // Mock instance of btGatt was created in initToConnectedProcessingState().
        BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
                mBassClientStateMachine.mBluetoothGatt;
        mBassClientStateMachine.mBluetoothGatt = null;
        mBassClientStateMachine.sendMessage(DISCONNECT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());

        mBassClientStateMachine.mBluetoothGatt = btGatt;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(DISCONNECT),
                BassClientStateMachine.Disconnected.class);
        verify(btGatt).disconnect();
        verify(btGatt).close();
    }

    @Test
    public void sendStateChangedMessage_inConnectedProcessingState() {
        initToConnectedProcessingState();

        Message msgToConnectedState =
                mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
        msgToConnectedState.obj = BluetoothProfile.STATE_CONNECTED;

        mBassClientStateMachine.sendMessage(msgToConnectedState);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());

        Message msgToNoneConnectedState =
                mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
        msgToNoneConnectedState.obj = BluetoothProfile.STATE_DISCONNECTING;
        sendMessageAndVerifyTransition(
                msgToNoneConnectedState, BassClientStateMachine.Disconnected.class);
    }

    /**
     * This also tests BassClientStateMachine#sendPendingCallbacks
     */
    @Test
    public void sendGattTxnProcessedMessage_inConnectedProcessingState() {
        initToConnectedProcessingState();
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);

        // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN)
        mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD;
        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.mAutoTriggered = false;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();

        // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD;
        mBassClientStateMachine.mAutoTriggered = true;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mAutoTriggered).isFalse();

        // Test sendPendingCallbacks(ADD_BCAST_SOURCE, ERROR_UNKNOWN)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = ADD_BCAST_SOURCE;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        verify(callbacks).notifySourceAddFailed(any(), any(), anyInt());

        // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, REASON_LOCAL_APP_REQUEST)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE;
        mBassClientStateMachine.mAutoTriggered = true;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_SUCCESS),
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mAutoTriggered).isFalse();

        // Test sendPendingCallbacks(UPDATE_BCAST_SOURCE, ERROR_UNKNOWN)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = UPDATE_BCAST_SOURCE;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        verify(callbacks).notifySourceModifyFailed(any(), anyInt(), anyInt());

        // Test sendPendingCallbacks(REMOVE_BCAST_SOURCE, ERROR_UNKNOWN)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        verify(callbacks).notifySourceRemoveFailed(any(), anyInt(), anyInt());

        // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        // Nothing to verify more

        // Test sendPendingCallbacks(SET_BCAST_CODE, REASON_LOCAL_APP_REQUEST)
        moveConnectedStateToConnectedProcessingState();
        mBassClientStateMachine.mPendingOperation = -1;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        // Nothing to verify more
    }

    @Test
    public void sendGattTxnTimeoutMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();

        mBassClientStateMachine.mPendingOperation = SET_BCAST_CODE;
        mBassClientStateMachine.mPendingSourceId = 0;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_TIMEOUT, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(-1);
        assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(-1);
    }

    @Test
    public void sendMessageForDeferring_inConnectedProcessingState_defersMessage() {
        initToConnectedProcessingState();

        mBassClientStateMachine.sendMessage(READ_BASS_CHARACTERISTICS);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(READ_BASS_CHARACTERISTICS))
                .isTrue();

        mBassClientStateMachine.sendMessage(START_SCAN_OFFLOAD);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(START_SCAN_OFFLOAD))
                .isTrue();

        mBassClientStateMachine.sendMessage(STOP_SCAN_OFFLOAD);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(STOP_SCAN_OFFLOAD))
                .isTrue();

        mBassClientStateMachine.sendMessage(SELECT_BCAST_SOURCE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SELECT_BCAST_SOURCE))
                .isTrue();

        mBassClientStateMachine.sendMessage(ADD_BCAST_SOURCE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(ADD_BCAST_SOURCE))
                .isTrue();

        mBassClientStateMachine.sendMessage(SET_BCAST_CODE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SET_BCAST_CODE))
                .isTrue();

        mBassClientStateMachine.sendMessage(REMOVE_BCAST_SOURCE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(REMOVE_BCAST_SOURCE))
                .isTrue();

        mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(PSYNC_ACTIVE_TIMEOUT))
                .isTrue();
    }

    @Test
    public void sendInvalidMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();

        mBassClientStateMachine.sendMessage(-1);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
    }

    private void initToDisconnectedState() {
        allowConnection(true);
        allowConnectGatt(true);
@@ -758,6 +958,23 @@ public class BassClientStateMachineTest {
        Mockito.clearInvocations(mBassClientService);
    }

    private void initToConnectedProcessingState() {
        initToConnectedState();
        moveConnectedStateToConnectedProcessingState();
    }

    private void moveConnectedStateToConnectedProcessingState() {
        BluetoothGattCharacteristic gattCharacteristic = Mockito.mock(
                BluetoothGattCharacteristic.class);
        BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
                BassClientStateMachine.BluetoothGattTestableWrapper.class);
        mBassClientStateMachine.mBluetoothGatt = btGatt;
        sendMessageAndVerifyTransition(mBassClientStateMachine.obtainMessage(
                        READ_BASS_CHARACTERISTICS, gattCharacteristic),
                BassClientStateMachine.ConnectedProcessing.class);
        Mockito.clearInvocations(mBassClientService);
    }

    private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
        Mockito.clearInvocations(mBassClientService);
        mBassClientStateMachine.sendMessage(msg);