blob: 7e0aca04fbe255e44cef93b9687fbeac6910f660 [file] [log] [blame]
/*
* Copyright 2022 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.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.android.bluetooth.bass_client.BassConstants.CLIENT_CHARACTERISTIC_CONFIG;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastChannel;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothLeBroadcastSubgroup;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.Intent;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import androidx.test.filters.MediumTest;
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@MediumTest
@RunWith(JUnit4.class)
public class BassClientStateMachineTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
private static final int CONNECTION_TIMEOUT_MS = 1_000;
private static final int TIMEOUT_MS = 2_000;
private static final int WAIT_MS = 1_200;
private static final String TEST_BROADCAST_NAME = "Test";
private BluetoothAdapter mAdapter;
private HandlerThread mHandlerThread;
private StubBassClientStateMachine mBassClientStateMachine;
private BluetoothDevice mTestDevice;
@Mock private AdapterService mAdapterService;
@Mock private BassClientService mBassClientService;
@Spy private BluetoothMethodProxy mMethodProxy;
@Before
public void setUp() throws Exception {
TestUtils.setAdapterService(mAdapterService);
mAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothMethodProxy.setInstanceForTesting(mMethodProxy);
doNothing().when(mMethodProxy).periodicAdvertisingManagerTransferSync(
any(), any(), anyInt(), anyInt());
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
// Set up thread and looper
mHandlerThread = new HandlerThread("BassClientStateMachineTestHandlerThread");
mHandlerThread.start();
mBassClientStateMachine = new StubBassClientStateMachine(mTestDevice,
mBassClientService, mHandlerThread.getLooper(), CONNECTION_TIMEOUT_MS);
mBassClientStateMachine.start();
}
@After
public void tearDown() throws Exception {
mBassClientStateMachine.doQuit();
mHandlerThread.quit();
TestUtils.clearAdapterService(mAdapterService);
}
/**
* Test that default state is disconnected
*/
@Test
public void testDefaultDisconnectedState() {
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
mBassClientStateMachine.getConnectionState());
}
/**
* Allow/disallow connection to any device.
*
* @param allow if true, connection is allowed
*/
private void allowConnection(boolean allow) {
when(mBassClientService.okToConnect(any(BluetoothDevice.class))).thenReturn(allow);
}
private void allowConnectGatt(boolean allow) {
mBassClientStateMachine.mShouldAllowGatt = allow;
}
/**
* Test that an incoming connection with policy forbidding connection is rejected
*/
@Test
public void testOkToConnectFails() {
allowConnection(false);
allowConnectGatt(true);
// Inject an event for when incoming connection is requested
mBassClientStateMachine.sendMessage(CONNECT);
// Verify that no connection state broadcast is executed
verify(mBassClientService, after(WAIT_MS).never()).sendBroadcast(any(Intent.class),
anyString());
// Check that we are in Disconnected state
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class));
}
@Test
public void testFailToConnectGatt() {
allowConnection(true);
allowConnectGatt(false);
// Inject an event for when incoming connection is requested
mBassClientStateMachine.sendMessage(CONNECT);
// Verify that no connection state broadcast is executed
verify(mBassClientService, after(WAIT_MS).never()).sendBroadcast(any(Intent.class),
anyString());
// Check that we are in Disconnected state
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(BassClientStateMachine.Disconnected.class));
assertNull(mBassClientStateMachine.mBluetoothGatt);
}
@Test
public void testSuccessfullyConnected() {
allowConnection(true);
allowConnectGatt(true);
// Inject an event for when incoming connection is requested
mBassClientStateMachine.sendMessage(CONNECT);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mBassClientService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
intentArgument1.capture(), anyString(), any(Bundle.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(BassClientStateMachine.Connecting.class));
assertNotNull(mBassClientStateMachine.mGattCallback);
mBassClientStateMachine.notifyConnectionStateChanged(
GATT_SUCCESS, BluetoothProfile.STATE_CONNECTED);
// Verify that the expected number of broadcasts are executed:
// - two calls to broadcastConnectionState(): Disconnected -> Connecting -> Connected
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mBassClientService, timeout(TIMEOUT_MS).times(2)).sendBroadcast(
intentArgument2.capture(), anyString(), any(Bundle.class));
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(BassClientStateMachine.Connected.class));
}
@Test
public void testConnectGattTimeout() {
allowConnection(true);
allowConnectGatt(true);
// Inject an event for when incoming connection is requested
mBassClientStateMachine.sendMessage(CONNECT);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mBassClientService, timeout(TIMEOUT_MS).times(1)).sendBroadcast(
intentArgument1.capture(), anyString(), any(Bundle.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(BassClientStateMachine.Connecting.class));
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mBassClientService, timeout(TIMEOUT_MS).times(
2)).sendBroadcast(intentArgument2.capture(), anyString(), any(Bundle.class));
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mBassClientStateMachine.getCurrentState(),
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(CONNECT),
BassClientStateMachine.Connecting.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(BassClientStateMachine.CONNECT_TIMEOUT),
BassClientStateMachine.Disconnected.class);
// disconnected -> connecting ---DISCONNECT---> disconnected
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(BassClientStateMachine.DISCONNECT),
BassClientStateMachine.Disconnected.class);
// disconnected -> connecting ---CONNECTION_STATE_CHANGED(connected)---> connected -->
// disconnected
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
CONNECTION_STATE_CHANGED,
Integer.valueOf(BluetoothProfile.STATE_CONNECTED)),
BassClientStateMachine.Connected.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
CONNECTION_STATE_CHANGED,
Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)),
BassClientStateMachine.Disconnected.class);
// disconnected -> connecting ---CONNECTION_STATE_CHANGED(non-connected) --> disconnected
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
CONNECTION_STATE_CHANGED,
Integer.valueOf(BluetoothProfile.STATE_DISCONNECTED)),
BassClientStateMachine.Disconnected.class);
// change default state to connected for the next tests
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
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(
READ_BASS_CHARACTERISTICS,
new BluetoothGattCharacteristic(UUID.randomUUID(),
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ)),
BassClientStateMachine.ConnectedProcessing.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED),
BassClientStateMachine.Connected.class);
// connected ----READ_BASS_CHARACTERISTICS---> connectedProcessing --GATT_TXN_TIMEOUT -->
// connected
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
READ_BASS_CHARACTERISTICS,
new BluetoothGattCharacteristic(UUID.randomUUID(),
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ)),
BassClientStateMachine.ConnectedProcessing.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(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(GATT_TXN_PROCESSED),
BassClientStateMachine.Connected.class);
// connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD),
BassClientStateMachine.ConnectedProcessing.class);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED),
BassClientStateMachine.Connected.class);
}
@Test
public void acquireAllBassChars() {
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
// Do nothing when mBluetoothGatt.getService returns null
mBassClientStateMachine.acquireAllBassChars();
BluetoothGattService gattService = Mockito.mock(BluetoothGattService.class);
when(btGatt.getService(BassConstants.BASS_UUID)).thenReturn(gattService);
List<BluetoothGattCharacteristic> characteristics = new ArrayList<>();
BluetoothGattCharacteristic scanControlPoint = new BluetoothGattCharacteristic(
BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT,
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ);
characteristics.add(scanControlPoint);
BluetoothGattCharacteristic bassCharacteristic = new BluetoothGattCharacteristic(
UUID.randomUUID(),
BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_READ);
characteristics.add(bassCharacteristic);
when(gattService.getCharacteristics()).thenReturn(characteristics);
mBassClientStateMachine.acquireAllBassChars();
assertThat(mBassClientStateMachine.mBroadcastScanControlPoint).isEqualTo(scanControlPoint);
assertThat(mBassClientStateMachine.mBroadcastCharacteristics).contains(bassCharacteristic);
}
@Test
public void simpleMethods() {
// dump() shouldn't crash
StringBuilder sb = new StringBuilder();
mBassClientStateMachine.dump(sb);
// log() shouldn't crash
String msg = "test-log-message";
mBassClientStateMachine.log(msg);
// messageWhatToString() shouldn't crash
for (int i = CONNECT; i <= CONNECT_TIMEOUT + 1; ++i) {
mBassClientStateMachine.messageWhatToString(i);
}
final int invalidSourceId = -100;
assertThat(mBassClientStateMachine.getCurrentBroadcastMetadata(invalidSourceId)).isNull();
assertThat(mBassClientStateMachine.getDevice()).isEqualTo(mTestDevice);
assertThat(mBassClientStateMachine.hasPendingSourceOperation()).isFalse();
assertThat(mBassClientStateMachine.isEmpty(new byte[] { 0 })).isTrue();
assertThat(mBassClientStateMachine.isEmpty(new byte[] { 1 })).isFalse();
assertThat(mBassClientStateMachine.isPendingRemove(invalidSourceId)).isFalse();
}
@Test
public void parseScanRecord_withoutBaseData_makesNoStopScanOffloadFalse() {
byte[] scanRecord = new byte[]{
0x02, 0x01, 0x1a, // advertising flags
0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids
0x04, 0x09, 0x50, 0x65, 0x64, // name
0x02, 0x0A, (byte) 0xec, // tx power level
0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data
0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
};
ScanRecord data = ScanRecord.parseFromBytes(scanRecord);
mBassClientStateMachine.mNoStopScanOffload = true;
mBassClientStateMachine.parseScanRecord(0, data);
assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
}
@Test
public void parseScanRecord_withBaseData_callsUpdateBase() {
byte[] scanRecordWithBaseData =
new byte[] {
(byte) 0x02,
(byte) 0x01,
(byte) 0x1a, // advertising flags
(byte) 0x05,
(byte) 0x02,
(byte) 0x51,
(byte) 0x18,
(byte) 0x0a,
(byte) 0x11, // 16 bit service uuids
(byte) 0x04,
(byte) 0x09,
(byte) 0x50,
(byte) 0x65,
(byte) 0x64, // name
(byte) 0x02,
(byte) 0x0A,
(byte) 0xec, // tx power level
(byte) 0x19,
(byte) 0x16,
(byte) 0x51,
(byte) 0x18, // service data (base data with 18 bytes)
// LEVEL 1
(byte) 0x01,
(byte) 0x02,
(byte) 0x03, // presentationDelay
(byte) 0x01, // numSubGroups
// LEVEL 2
(byte) 0x01, // numSubGroups
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00, // UNKNOWN_CODEC
(byte) 0x02, // codecConfigLength
(byte) 0x01,
(byte) 'A', // codecConfigInfo
(byte) 0x03, // metaDataLength
(byte) 0x06,
(byte) 0x07,
(byte) 0x08, // metaData
// LEVEL 3
(byte) 0x04, // index
(byte) 0x03, // codecConfigLength
(byte) 0x02,
(byte) 'B',
(byte) 'C', // codecConfigInfo
(byte) 0x05,
(byte) 0xff,
(byte) 0xe0,
(byte) 0x00,
(byte) 0x02,
(byte) 0x15, // manufacturer specific data
(byte) 0x03,
(byte) 0x50,
(byte) 0x01,
(byte) 0x02, // an unknown data type won't cause trouble
};
ScanRecord data = ScanRecord.parseFromBytes(scanRecordWithBaseData);
assertThat(data.getServiceUuids()).contains(BassConstants.BASIC_AUDIO_UUID);
assertThat(data.getServiceData(BassConstants.BASIC_AUDIO_UUID)).isNotNull();
mBassClientStateMachine.parseScanRecord(0, data);
verify(mBassClientService).updateBase(anyInt(), any());
}
@Test
public void gattCallbackOnConnectionStateChange_changedToConnected()
throws InterruptedException {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
// disallow connection
allowConnection(false);
int status = BluetoothProfile.STATE_CONNECTING;
int newState = BluetoothProfile.STATE_CONNECTED;
cb.onConnectionStateChange(null, status, newState);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(btGatt).disconnect();
verify(btGatt).close();
assertThat(mBassClientStateMachine.mBluetoothGatt).isNull();
assertThat(mBassClientStateMachine.mMsgWhats).contains(CONNECTION_STATE_CHANGED);
mBassClientStateMachine.mMsgWhats.clear();
mBassClientStateMachine.mBluetoothGatt = btGatt;
allowConnection(true);
mBassClientStateMachine.mDiscoveryInitiated = false;
status = BluetoothProfile.STATE_DISCONNECTED;
newState = BluetoothProfile.STATE_CONNECTED;
cb.onConnectionStateChange(null, status, newState);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mDiscoveryInitiated).isTrue();
assertThat(mBassClientStateMachine.mMsgWhats).contains(CONNECTION_STATE_CHANGED);
assertThat(mBassClientStateMachine.mMsgObj).isEqualTo(newState);
mBassClientStateMachine.mMsgWhats.clear();
}
@Test
public void gattCallbackOnConnectionStateChanged_changedToDisconnected()
throws InterruptedException {
initToConnectingState();
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
allowConnection(false);
int status = BluetoothProfile.STATE_CONNECTING;
int newState = BluetoothProfile.STATE_DISCONNECTED;
cb.onConnectionStateChange(null, status, newState);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mMsgWhats).contains(CONNECTION_STATE_CHANGED);
assertThat(mBassClientStateMachine.mMsgObj).isEqualTo(newState);
mBassClientStateMachine.mMsgWhats.clear();
}
@Test
public void gattCallbackOnServicesDiscovered() {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
// Do nothing if mDiscoveryInitiated is false.
mBassClientStateMachine.mDiscoveryInitiated = false;
int status = GATT_FAILURE;
cb.onServicesDiscovered(null, status);
verify(btGatt, never()).requestMtu(anyInt());
// Do nothing if status is not GATT_SUCCESS.
mBassClientStateMachine.mDiscoveryInitiated = true;
status = GATT_FAILURE;
cb.onServicesDiscovered(null, status);
verify(btGatt, never()).requestMtu(anyInt());
// call requestMtu() if status is GATT_SUCCESS.
mBassClientStateMachine.mDiscoveryInitiated = true;
status = GATT_SUCCESS;
cb.onServicesDiscovered(null, status);
verify(btGatt).requestMtu(anyInt());
}
/**
* This also tests BassClientStateMachine#processBroadcastReceiverState.
*/
@Test
public void gattCallbackOnCharacteristicRead() {
mBassClientStateMachine.mShouldHandleMessage = false;
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BluetoothGattDescriptor desc = Mockito.mock(BluetoothGattDescriptor.class);
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
BluetoothGattCharacteristic characteristic =
Mockito.mock(BluetoothGattCharacteristic.class);
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
when(characteristic.getUuid()).thenReturn(BassConstants.BASS_BCAST_RECEIVER_STATE);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
// Characteristic read success with null value
when(characteristic.getValue()).thenReturn(null);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
verify(characteristic, never()).getDescriptor(any());
// Characteristic read failed and mBluetoothGatt is null.
mBassClientStateMachine.mBluetoothGatt = null;
cb.onCharacteristicRead(null, characteristic, GATT_FAILURE);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mMsgWhats).contains(GATT_TXN_PROCESSED);
assertThat(mBassClientStateMachine.mMsgAgr1).isEqualTo(GATT_FAILURE);
mBassClientStateMachine.mMsgWhats.clear();
// Characteristic read failed and mBluetoothGatt is not null.
mBassClientStateMachine.mBluetoothGatt = btGatt;
when(characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG)).thenReturn(desc);
cb.onCharacteristicRead(null, characteristic, GATT_FAILURE);
verify(btGatt).setCharacteristicNotification(any(), anyBoolean());
verify(btGatt).writeDescriptor(desc);
verify(desc).setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
// Tests for processBroadcastReceiverState
int sourceId = 1;
byte[] value = new byte[] { };
mBassClientStateMachine.mNumOfBroadcastReceiverStates = 2;
mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE;
mBassClientStateMachine.mPendingSourceId = (byte) sourceId;
when(characteristic.getValue()).thenReturn(value);
when(characteristic.getInstanceId()).thenReturn(sourceId);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
mBassClientStateMachine.mPendingOperation = 0;
mBassClientStateMachine.mPendingSourceId = 0;
sourceId = 2; // mNextId would become 2
when(characteristic.getInstanceId()).thenReturn(sourceId);
Mockito.clearInvocations(callbacks);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
mBassClientStateMachine.mPendingMetadata = createBroadcastMetadata();
sourceId = 1;
value = new byte[] {
(byte) sourceId, // sourceId
0x00, // sourceAddressType
0x01, 0x02, 0x03, 0x04, 0x05, 0x00, // sourceAddress
0x00, // sourceAdvSid
0x00, 0x00, 0x00, // broadcastIdBytes
(byte) BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_NO_PAST,
(byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE,
// 16 bytes badBroadcastCode
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, // numSubGroups
// SubGroup #1
0x00, 0x00, 0x00, 0x00, // audioSyncIndex
0x02, // metaDataLength
0x00, 0x00, // metadata
};
when(characteristic.getValue()).thenReturn(value);
when(characteristic.getInstanceId()).thenReturn(sourceId);
Mockito.clearInvocations(callbacks);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifySourceAdded(any(), any(), anyInt());
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
assertThat(mBassClientStateMachine.mMsgWhats).contains(STOP_SCAN_OFFLOAD);
// set some values for covering more lines of processPASyncState()
mBassClientStateMachine.mPendingMetadata = null;
mBassClientStateMachine.mSetBroadcastCodePending = true;
mBassClientStateMachine.mIsPendingRemove = true;
value[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX] =
BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST;
value[BassConstants.BCAST_RCVR_STATE_ENC_STATUS_IDX] =
BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED;
value[35] = 0; // set metaDataLength of subgroup #1 0
PeriodicAdvertisementResult paResult = Mockito.mock(PeriodicAdvertisementResult.class);
when(characteristic.getValue()).thenReturn(value);
when(mBassClientService.getPeriodicAdvertisementResult(any())).thenReturn(paResult);
when(paResult.getSyncHandle()).thenReturn(100);
Mockito.clearInvocations(callbacks);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
assertThat(mBassClientStateMachine.mMsgWhats).contains(REMOVE_BCAST_SOURCE);
mBassClientStateMachine.mIsPendingRemove = null;
// set some values for covering more lines of processPASyncState()
mBassClientStateMachine.mPendingMetadata = createBroadcastMetadata();
for (int i = 0; i < BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE; ++i) {
value[BassConstants.BCAST_RCVR_STATE_SRC_ADDR_START_IDX + i] = 0x00;
}
when(mBassClientService.getPeriodicAdvertisementResult(any())).thenReturn(null);
when(mBassClientService.isLocalBroadcast(any())).thenReturn(true);
when(characteristic.getValue()).thenReturn(value);
Mockito.clearInvocations(callbacks);
cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifySourceRemoved(any(), anyInt(), anyInt());
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
}
@Test
public void gattCallbackOnCharacteristicChanged() {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
mBassClientStateMachine.mNumOfBroadcastReceiverStates = 1;
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
BluetoothGattCharacteristic characteristic =
Mockito.mock(BluetoothGattCharacteristic.class);
when(characteristic.getUuid()).thenReturn(BassConstants.BASS_BCAST_RECEIVER_STATE);
when(characteristic.getValue()).thenReturn(null);
cb.onCharacteristicChanged(null, characteristic);
verify(characteristic, atLeast(1)).getUuid();
verify(characteristic).getValue();
verify(callbacks, never()).notifyReceiveStateChanged(any(), anyInt(), any());
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mBassClientStateMachine.mNumOfBroadcastReceiverStates = 1;
Mockito.clearInvocations(characteristic);
when(characteristic.getValue()).thenReturn(new byte[] { });
cb.onCharacteristicChanged(null, characteristic);
verify(characteristic, atLeast(1)).getUuid();
verify(characteristic, atLeast(1)).getValue();
verify(callbacks).notifyReceiveStateChanged(any(), anyInt(), any());
}
@Test
public void gattCharacteristicWrite() {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BluetoothGattCharacteristic characteristic =Mockito.mock(BluetoothGattCharacteristic.class);
when(characteristic.getUuid()).thenReturn(BassConstants.BASS_BCAST_AUDIO_SCAN_CTRL_POINT);
cb.onCharacteristicWrite(null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mMsgWhats).contains(GATT_TXN_PROCESSED);
}
@Test
public void gattCallbackOnDescriptorWrite() {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
BluetoothGattDescriptor descriptor = Mockito.mock(BluetoothGattDescriptor.class);
when(descriptor.getUuid()).thenReturn(BassConstants.CLIENT_CHARACTERISTIC_CONFIG);
cb.onDescriptorWrite(null, descriptor, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mMsgWhats).contains(GATT_TXN_PROCESSED);
}
@Test
public void gattCallbackOnMtuChanged() {
mBassClientStateMachine.connectGatt(true);
BluetoothGattCallback cb = mBassClientStateMachine.mGattCallback;
mBassClientStateMachine.mMTUChangeRequested = true;
cb.onMtuChanged(null, 10, GATT_SUCCESS);
assertThat(mBassClientStateMachine.mMTUChangeRequested).isTrue();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
cb.onMtuChanged(null, 10, GATT_SUCCESS);
assertThat(mBassClientStateMachine.mMTUChangeRequested).isFalse();
}
@Test
public void sendConnectMessage_inDisconnectedState() {
initToDisconnectedState();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
verify(btGatt).disconnect();
verify(btGatt).close();
}
@Test
public void sendDisconnectMessage_inDisconnectedState() {
initToDisconnectedState();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
mBassClientStateMachine.sendMessage(DISCONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(btGatt).disconnect();
verify(btGatt).close();
}
@Test
public void sendStateChangedMessage_inDisconnectedState() {
initToDisconnectedState();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
Message msgToConnectingState =
mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msgToConnectingState.obj = BluetoothProfile.STATE_CONNECTING;
mBassClientStateMachine.sendMessage(msgToConnectingState);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
Message msgToConnectedState =
mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msgToConnectedState.obj = BluetoothProfile.STATE_CONNECTED;
sendMessageAndVerifyTransition(msgToConnectedState, BassClientStateMachine.Connected.class);
}
@Test
public void sendOtherMessages_inDisconnectedState_doesNotChangeState() {
initToDisconnectedState();
mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
mBassClientStateMachine.sendMessage(-1);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
@Test
public void sendConnectMessages_inConnectingState_doesNotChangeState() {
initToConnectingState();
mBassClientStateMachine.sendMessage(CONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
@Test
public void sendDisconnectMessages_inConnectingState_defersMessage() {
initToConnectingState();
mBassClientStateMachine.sendMessage(DISCONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(DISCONNECT)).isTrue();
}
@Test
public void sendReadBassCharacteristicsMessage_inConnectingState_defersMessage() {
initToConnectingState();
mBassClientStateMachine.sendMessage(READ_BASS_CHARACTERISTICS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(READ_BASS_CHARACTERISTICS))
.isTrue();
}
@Test
public void sendPsyncActiveTimeoutMessage_inConnectingState_defersMessage() {
initToConnectingState();
mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(PSYNC_ACTIVE_TIMEOUT)).isTrue();
}
@Test
public void sendStateChangedToNonConnectedMessage_inConnectingState_movesToDisconnected() {
initToConnectingState();
Message msg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msg.obj = BluetoothProfile.STATE_CONNECTING;
BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
sendMessageAndVerifyTransition(msg, BassClientStateMachine.Disconnected.class);
verify(btGatt).close();
assertNull(mBassClientStateMachine.mBluetoothGatt);
}
@Test
public void sendStateChangedToConnectedMessage_inConnectingState_movesToConnected() {
initToConnectingState();
Message msg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msg.obj = BluetoothProfile.STATE_CONNECTED;
sendMessageAndVerifyTransition(msg, BassClientStateMachine.Connected.class);
}
@Test
public void sendConnectTimeMessage_inConnectingState() {
initToConnectingState();
Message timeoutWithDifferentDevice = mBassClientStateMachine.obtainMessage(CONNECT_TIMEOUT,
mAdapter.getRemoteDevice("00:00:00:00:00:00"));
mBassClientStateMachine.sendMessage(timeoutWithDifferentDevice);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
Message msg = mBassClientStateMachine.obtainMessage(CONNECT_TIMEOUT, mTestDevice);
sendMessageAndVerifyTransition(msg, BassClientStateMachine.Disconnected.class);
verify(btGatt).close();
assertNull(mBassClientStateMachine.mBluetoothGatt);
}
@Test
public void sendInvalidMessage_inConnectingState_doesNotChangeState() {
initToConnectingState();
mBassClientStateMachine.sendMessage(-1);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
@Test
public void sendConnectMessage_inConnectedState() {
initToConnectedState();
mBassClientStateMachine.sendMessage(CONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
@Test
public void sendDisconnectMessage_inConnectedState() {
initToConnectedState();
mBassClientStateMachine.mBluetoothGatt = null;
mBassClientStateMachine.sendMessage(DISCONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(DISCONNECT),
BassClientStateMachine.Disconnected.class);
verify(btGatt).disconnect();
verify(btGatt).close();
}
@Test
public void sendStateChangedMessage_inConnectedState() {
initToConnectedState();
Message connectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
connectedMsg.obj = BluetoothProfile.STATE_CONNECTED;
mBassClientStateMachine.sendMessage(connectedMsg);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
Message noneConnectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
noneConnectedMsg.obj = BluetoothProfile.STATE_DISCONNECTING;
sendMessageAndVerifyTransition(noneConnectedMsg, BassClientStateMachine.Disconnected.class);
verify(btGatt).close();
assertNull(mBassClientStateMachine.mBluetoothGatt);
}
@Test
public void sendReadBassCharacteristicsMessage_inConnectedState() {
initToConnectedState();
BluetoothGattCharacteristic gattCharacteristic = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.sendMessage(READ_BASS_CHARACTERISTICS, gattCharacteristic);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
sendMessageAndVerifyTransition(mBassClientStateMachine.obtainMessage(
READ_BASS_CHARACTERISTICS, gattCharacteristic),
BassClientStateMachine.ConnectedProcessing.class);
}
@Test
public void sendStartScanOffloadMessage_inConnectedState() {
initToConnectedState();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
mBassClientStateMachine.sendMessage(START_SCAN_OFFLOAD);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(START_SCAN_OFFLOAD),
BassClientStateMachine.ConnectedProcessing.class);
verify(btGatt).writeCharacteristic(scanControlPoint);
verify(scanControlPoint).setValue(REMOTE_SCAN_START);
}
@Test
public void sendStopScanOffloadMessage_inConnectedState() {
initToConnectedState();
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
mBassClientStateMachine.sendMessage(STOP_SCAN_OFFLOAD);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD),
BassClientStateMachine.ConnectedProcessing.class);
verify(btGatt).writeCharacteristic(scanControlPoint);
verify(scanControlPoint).setValue(REMOTE_SCAN_STOP);
}
@Test
public void sendPsyncActiveMessage_inConnectedState() {
initToConnectedState();
mBassClientStateMachine.mNoStopScanOffload = true;
mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
}
@Test
public void sendInvalidMessage_inConnectedState_doesNotChangeState() {
initToConnectedState();
mBassClientStateMachine.sendMessage(-1);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
@Test
public void sendSelectBcastSourceMessage_inConnectedState() {
initToConnectedState();
byte[] scanRecord = new byte[]{
0x02, 0x01, 0x1a, // advertising flags
0x05, 0x02, 0x52, 0x18, 0x0a, 0x11, // 16 bit service uuids
0x04, 0x09, 0x50, 0x65, 0x64, // name
0x02, 0x0A, (byte) 0xec, // tx power level
0x05, 0x30, 0x54, 0x65, 0x73, 0x74, // broadcast name: Test
0x06, 0x16, 0x52, 0x18, 0x50, 0x64, 0x65, // service data
0x08, 0x16, 0x56, 0x18, 0x07, 0x03, 0x06, 0x07, 0x08,
// service data - public broadcast,
// feature - 0x7, metadata len - 0x3, metadata - 0x6, 0x7, 0x8
0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
};
ScanRecord record = ScanRecord.parseFromBytes(scanRecord);
doNothing().when(mMethodProxy).periodicAdvertisingManagerRegisterSync(
any(), any(), anyInt(), anyInt(), any(), any());
ScanResult scanResult = new ScanResult(mTestDevice, 0, 0, 0, 0, 0, 0, 0, record, 0);
mBassClientStateMachine.sendMessage(
SELECT_BCAST_SOURCE, BassConstants.AUTO, 0, scanResult);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService).updatePeriodicAdvertisementResultMap(
any(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(),
any(), eq(TEST_BROADCAST_NAME));
}
@Test
public void sendAddBcastSourceMessage_inConnectedState() {
initToConnectedState();
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
BluetoothLeBroadcastMetadata metadata = createBroadcastMetadata();
mBassClientStateMachine.sendMessage(ADD_BCAST_SOURCE, metadata);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mBassClientService).getCallbacks();
verify(callbacks).notifySourceAddFailed(any(), any(), anyInt());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(ADD_BCAST_SOURCE, metadata),
BassClientStateMachine.ConnectedProcessing.class);
verify(scanControlPoint).setValue(any(byte[].class));
verify(btGatt).writeCharacteristic(any());
}
@Test
public void sendUpdateBcastSourceMessage_inConnectedState() {
initToConnectedState();
mBassClientStateMachine.connectGatt(true);
mBassClientStateMachine.mNumOfBroadcastReceiverStates = 2;
// Prepare mBluetoothLeBroadcastReceiveStates for test
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
int sourceId = 1;
int paSync = BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE;
byte[] value = new byte[] {
(byte) sourceId, // sourceId
0x00, // sourceAddressType
0x01, 0x02, 0x03, 0x04, 0x05, 0x00, // sourceAddress
0x00, // sourceAdvSid
0x00, 0x00, 0x00, // broadcastIdBytes
(byte) paSync,
(byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE,
// 16 bytes badBroadcastCode
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, // numSubGroups
// SubGroup #1
0x00, 0x00, 0x00, 0x00, // audioSyncIndex
0x02, // metaDataLength
0x00, 0x00, // metadata
};
BluetoothGattCharacteristic characteristic =
Mockito.mock(BluetoothGattCharacteristic.class);
when(characteristic.getValue()).thenReturn(value);
when(characteristic.getInstanceId()).thenReturn(sourceId);
when(characteristic.getUuid()).thenReturn(BassConstants.BASS_BCAST_RECEIVER_STATE);
mBassClientStateMachine.mGattCallback.onCharacteristicRead(
null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
BluetoothLeBroadcastMetadata metadata = createBroadcastMetadata();
when(mBassClientService.getPeriodicAdvertisementResult(any())).thenReturn(null);
mBassClientStateMachine.sendMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, metadata);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
PeriodicAdvertisementResult paResult = Mockito.mock(PeriodicAdvertisementResult.class);
when(mBassClientService.getPeriodicAdvertisementResult(any())).thenReturn(paResult);
when(mBassClientService.getBase(anyInt())).thenReturn(null);
Mockito.clearInvocations(callbacks);
mBassClientStateMachine.sendMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, metadata);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
BaseData data = Mockito.mock(BaseData.class);
when(mBassClientService.getBase(anyInt())).thenReturn(data);
when(data.getNumberOfSubgroupsofBIG()).thenReturn((byte) 1);
Mockito.clearInvocations(callbacks);
mBassClientStateMachine.sendMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, metadata);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifySourceModifyFailed(any(), anyInt(), anyInt());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
mBassClientStateMachine.mPendingOperation = 0;
mBassClientStateMachine.mPendingSourceId = 0;
mBassClientStateMachine.mPendingMetadata = null;
Mockito.clearInvocations(callbacks);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(
UPDATE_BCAST_SOURCE, sourceId, paSync, metadata),
BassClientStateMachine.ConnectedProcessing.class);
assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE);
assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sourceId);
assertThat(mBassClientStateMachine.mPendingMetadata).isEqualTo(metadata);
}
@Test
public void sendSetBcastCodeMessage_inConnectedState() {
initToConnectedState();
mBassClientStateMachine.connectGatt(true);
mBassClientStateMachine.mNumOfBroadcastReceiverStates = 2;
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
// Prepare mBluetoothLeBroadcastReceiveStates with metadata for test
mBassClientStateMachine.mShouldHandleMessage = false;
int sourceId = 1;
byte[] value = new byte[] {
(byte) sourceId, // sourceId
0x00, // sourceAddressType
0x01, 0x02, 0x03, 0x04, 0x05, 0x00, // sourceAddress
0x00, // sourceAdvSid
0x00, 0x00, 0x00, // broadcastIdBytes
(byte) BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
(byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED,
// 16 bytes badBroadcastCode
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, // numSubGroups
// SubGroup #1
0x00, 0x00, 0x00, 0x00, // audioSyncIndex
0x02, // metaDataLength
0x00, 0x00, // metadata
};
mBassClientStateMachine.mPendingOperation = REMOVE_BCAST_SOURCE;
mBassClientStateMachine.mPendingSourceId = (byte) sourceId;
BluetoothGattCharacteristic characteristic =
Mockito.mock(BluetoothGattCharacteristic.class);
when(characteristic.getValue()).thenReturn(value);
when(characteristic.getInstanceId()).thenReturn(sourceId);
when(characteristic.getUuid()).thenReturn(BassConstants.BASS_BCAST_RECEIVER_STATE);
mBassClientStateMachine.mGattCallback.onCharacteristicRead(
null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mBassClientStateMachine.mPendingMetadata = createBroadcastMetadata();
mBassClientStateMachine.mGattCallback.onCharacteristicRead(
null, characteristic, GATT_SUCCESS);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mBassClientStateMachine.mShouldHandleMessage = true;
BluetoothLeBroadcastReceiveState recvState = new BluetoothLeBroadcastReceiveState(
2,
BluetoothDevice.ADDRESS_TYPE_PUBLIC,
mAdapter.getRemoteLeDevice("00:00:00:00:00:00",
BluetoothDevice.ADDRESS_TYPE_PUBLIC),
0,
0,
BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED,
null,
0,
Arrays.asList(new Long[0]),
Arrays.asList(new BluetoothLeAudioContentMetadata[0])
);
mBassClientStateMachine.mSetBroadcastCodePending = false;
mBassClientStateMachine.sendMessage(SET_BCAST_CODE, recvState);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
assertThat(mBassClientStateMachine.mSetBroadcastCodePending).isTrue();
recvState = new BluetoothLeBroadcastReceiveState(
sourceId,
BluetoothDevice.ADDRESS_TYPE_PUBLIC,
mAdapter.getRemoteLeDevice("00:00:00:00:00:00",
BluetoothDevice.ADDRESS_TYPE_PUBLIC),
0,
0,
BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED,
null,
0,
Arrays.asList(new Long[0]),
Arrays.asList(new BluetoothLeAudioContentMetadata[0])
);
mBassClientStateMachine.sendMessage(SET_BCAST_CODE, recvState);
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(SET_BCAST_CODE, recvState),
BassClientStateMachine.ConnectedProcessing.class);
assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(SET_BCAST_CODE);
assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sourceId);
verify(btGatt).writeCharacteristic(any());
verify(scanControlPoint).setValue(any(byte[].class));
}
@Test
public void sendRemoveBcastSourceMessage_inConnectedState() {
initToConnectedState();
BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
when(mBassClientService.getCallbacks()).thenReturn(callbacks);
int sid = 10;
mBassClientStateMachine.sendMessage(REMOVE_BCAST_SOURCE, sid);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(callbacks).notifySourceRemoveFailed(any(), anyInt(), anyInt());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(
BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
BluetoothGattCharacteristic scanControlPoint = Mockito.mock(
BluetoothGattCharacteristic.class);
mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(REMOVE_BCAST_SOURCE, sid),
BassClientStateMachine.ConnectedProcessing.class);
verify(scanControlPoint).setValue(any(byte[].class));
verify(btGatt).writeCharacteristic(any());
assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(REMOVE_BCAST_SOURCE);
assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sid);
}
@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());
BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
mBassClientStateMachine.mBluetoothGatt = btGatt;
Message msgToNoneConnectedState =
mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msgToNoneConnectedState.obj = BluetoothProfile.STATE_DISCONNECTING;
sendMessageAndVerifyTransition(
msgToNoneConnectedState, BassClientStateMachine.Disconnected.class);
verify(btGatt).close();
assertNull(mBassClientStateMachine.mBluetoothGatt);
}
/**
* 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());
}
@Test
public void dump_doesNotCrash() {
mBassClientStateMachine.dump(new StringBuilder());
}
private void initToDisconnectedState() {
allowConnection(true);
allowConnectGatt(true);
assertThat(mBassClientStateMachine.getCurrentState())
.isInstanceOf(BassClientStateMachine.Disconnected.class);
}
private void initToConnectingState() {
allowConnection(true);
allowConnectGatt(true);
sendMessageAndVerifyTransition(
mBassClientStateMachine.obtainMessage(CONNECT),
BassClientStateMachine.Connecting.class);
Mockito.clearInvocations(mBassClientService);
}
private void initToConnectedState() {
initToConnectingState();
Message msg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
msg.obj = BluetoothProfile.STATE_CONNECTED;
sendMessageAndVerifyTransition(msg, BassClientStateMachine.Connected.class);
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);
// 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));
}
private BluetoothLeBroadcastMetadata createBroadcastMetadata() {
final String testMacAddress = "00:11:22:33:44:55";
final int testBroadcastId = 42;
final int testAdvertiserSid = 1234;
final int testPaSyncInterval = 100;
final int testPresentationDelayMs = 345;
BluetoothDevice testDevice =
mAdapter.getRemoteLeDevice(testMacAddress, BluetoothDevice.ADDRESS_TYPE_RANDOM);
BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
.setEncrypted(false)
.setSourceDevice(testDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
.setSourceAdvertisingSid(testAdvertiserSid)
.setBroadcastId(testBroadcastId)
.setBroadcastCode(new byte[] { 0x00, 0x01, 0x00, 0x02 })
.setPaSyncInterval(testPaSyncInterval)
.setPresentationDelayMicros(testPresentationDelayMs);
// builder expect at least one subgroup
builder.addSubgroup(createBroadcastSubgroup());
return builder.build();
}
private BluetoothLeBroadcastSubgroup createBroadcastSubgroup() {
final long testAudioLocationFrontLeft = 0x01;
final long testAudioLocationFrontRight = 0x02;
// For BluetoothLeAudioContentMetadata
final String testProgramInfo = "Test";
// German language code in ISO 639-3
final String testLanguage = "deu";
final int testCodecId = 42;
final int testChannelIndex = 56;
BluetoothLeAudioCodecConfigMetadata codecMetadata =
new BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(testAudioLocationFrontLeft).build();
BluetoothLeAudioContentMetadata contentMetadata =
new BluetoothLeAudioContentMetadata.Builder()
.setProgramInfo(testProgramInfo).setLanguage(testLanguage).build();
BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
.setCodecId(testCodecId)
.setCodecSpecificConfig(codecMetadata)
.setContentMetadata(contentMetadata);
BluetoothLeAudioCodecConfigMetadata channelCodecMetadata =
new BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(testAudioLocationFrontRight).build();
// builder expect at least one channel
BluetoothLeBroadcastChannel channel =
new BluetoothLeBroadcastChannel.Builder()
.setSelected(true)
.setChannelIndex(testChannelIndex)
.setCodecMetadata(channelCodecMetadata)
.build();
builder.addChannel(channel);
return builder.build();
}
// It simulates GATT connection for testing.
public static class StubBassClientStateMachine extends BassClientStateMachine {
boolean mShouldAllowGatt = true;
boolean mShouldHandleMessage = true;
Boolean mIsPendingRemove;
List<Integer> mMsgWhats = new ArrayList<>();
int mMsgWhat;
int mMsgAgr1;
int mMsgArg2;
Object mMsgObj;
StubBassClientStateMachine(BluetoothDevice device, BassClientService service, Looper looper,
int connectTimeout) {
super(device, service, looper, connectTimeout);
}
@Override
public boolean connectGatt(Boolean autoConnect) {
mGattCallback = new GattCallback();
return mShouldAllowGatt;
}
@Override
public void sendMessage(Message msg) {
mMsgWhats.add(msg.what);
mMsgWhat = msg.what;
mMsgAgr1 = msg.arg1;
mMsgArg2 = msg.arg2;
mMsgObj = msg.obj;
if (mShouldHandleMessage) {
super.sendMessage(msg);
}
}
public void notifyConnectionStateChanged(int status, int newState) {
if (mGattCallback != null) {
BluetoothGatt gatt = null;
if (mBluetoothGatt != null) {
gatt = mBluetoothGatt.mWrappedBluetoothGatt;
}
mGattCallback.onConnectionStateChange(gatt, status, newState);
}
}
public boolean hasDeferredMessagesSuper(int what) {
return super.hasDeferredMessages(what);
}
@Override
boolean isPendingRemove(Integer sourceId) {
if (mIsPendingRemove == null) {
return super.isPendingRemove(sourceId);
}
return mIsPendingRemove;
}
}
}