Loading android/app/src/com/android/bluetooth/BluetoothMethodProxy.java +15 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.bluetooth; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingManager; import android.bluetooth.le.ScanResult; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; Loading @@ -27,6 +30,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.provider.Telephony; import android.util.Log; Loading Loading @@ -180,4 +184,14 @@ public class BluetoothMethodProxy { public long telephonyGetOrCreateThreadId(Context context, Set<String> recipients) { return Telephony.Threads.getOrCreateThreadId(context, recipients); } /** * Proxies {@link PeriodicAdvertisingManager#registerSync(ScanResult, int, int, * PeriodicAdvertisingCallback, Handler)}. */ public void periodicAdvertisingManagerRegisterSync(PeriodicAdvertisingManager manager, ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback, Handler handler) { manager.registerSync(scanResult, skip, timeout, callback, handler); } } android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +8 −4 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ import android.os.ParcelUuid; import android.provider.DeviceConfig; import android.util.Log; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; Loading @@ -113,8 +114,10 @@ import java.util.stream.IntStream; @VisibleForTesting public class BassClientStateMachine extends StateMachine { private static final String TAG = "BassClientStateMachine"; private static final byte[] REMOTE_SCAN_STOP = {00}; private static final byte[] REMOTE_SCAN_START = {01}; @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00}; @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01}; private static final byte OPCODE_ADD_SOURCE = 0x02; private static final byte OPCODE_UPDATE_SOURCE = 0x03; private static final byte OPCODE_SET_BCAST_PIN = 0x04; Loading Loading @@ -387,8 +390,9 @@ public class BassClientStateMachine extends StateMachine { mNoStopScanOffload = true; cancelActiveSync(null); try { mPeriodicAdvManager.registerSync(scanRes, 0, BassConstants.PSYNC_TIMEOUT, mPeriodicAdvCallback); BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync( mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT, mPeriodicAdvCallback, null); } catch (IllegalArgumentException ex) { Log.w(TAG, "registerSync:IllegalArgumentException"); Message message = obtainMessage(STOP_SCAN_OFFLOAD); Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +211 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,10 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_T import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; 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.START_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.STOP_SCAN_OFFLOAD; import static com.google.common.truth.Truth.assertThat; Loading @@ -43,6 +47,11 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothLeAudioCodecConfigMetadata; import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeBroadcastChannel; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.bluetooth.BluetoothProfile; import android.bluetooth.le.ScanRecord; import android.content.Intent; Loading @@ -54,6 +63,7 @@ import android.telecom.Log; import androidx.test.filters.MediumTest; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -68,6 +78,7 @@ 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; Loading @@ -88,16 +99,17 @@ public class BassClientStateMachineTest { private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private BluetoothDevice mTestDevice; @Mock private AdapterService mAdapterService; @Mock private BassClientService mBassClientService; @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); // Get a device for testing mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); Loading Loading @@ -345,7 +357,7 @@ public class BassClientStateMachineTest { // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD), mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), Loading Loading @@ -595,6 +607,132 @@ public class BassClientStateMachineTest { 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()); Message noneConnectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); noneConnectedMsg.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition(noneConnectedMsg, BassClientStateMachine.Disconnected.class); } @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()); } // TODO: add sendMessage tests for BCAST related messages private void initToDisconnectedState() { allowConnection(true); allowConnectGatt(true); Loading @@ -611,6 +749,15 @@ public class BassClientStateMachineTest { 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 <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); Loading @@ -621,6 +768,65 @@ public class BassClientStateMachineTest { 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(null) .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; Loading Loading
android/app/src/com/android/bluetooth/BluetoothMethodProxy.java +15 −1 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.bluetooth; import android.annotation.RequiresPermission; import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.PeriodicAdvertisingManager; import android.bluetooth.le.ScanResult; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; Loading @@ -27,6 +30,7 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.provider.Telephony; import android.util.Log; Loading Loading @@ -180,4 +184,14 @@ public class BluetoothMethodProxy { public long telephonyGetOrCreateThreadId(Context context, Set<String> recipients) { return Telephony.Threads.getOrCreateThreadId(context, recipients); } /** * Proxies {@link PeriodicAdvertisingManager#registerSync(ScanResult, int, int, * PeriodicAdvertisingCallback, Handler)}. */ public void periodicAdvertisingManagerRegisterSync(PeriodicAdvertisingManager manager, ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback, Handler handler) { manager.registerSync(scanResult, skip, timeout, callback, handler); } }
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +8 −4 Original line number Diff line number Diff line Loading @@ -88,6 +88,7 @@ import android.os.ParcelUuid; import android.provider.DeviceConfig; import android.util.Log; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ServiceFactory; Loading @@ -113,8 +114,10 @@ import java.util.stream.IntStream; @VisibleForTesting public class BassClientStateMachine extends StateMachine { private static final String TAG = "BassClientStateMachine"; private static final byte[] REMOTE_SCAN_STOP = {00}; private static final byte[] REMOTE_SCAN_START = {01}; @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00}; @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01}; private static final byte OPCODE_ADD_SOURCE = 0x02; private static final byte OPCODE_UPDATE_SOURCE = 0x03; private static final byte OPCODE_SET_BCAST_PIN = 0x04; Loading Loading @@ -387,8 +390,9 @@ public class BassClientStateMachine extends StateMachine { mNoStopScanOffload = true; cancelActiveSync(null); try { mPeriodicAdvManager.registerSync(scanRes, 0, BassConstants.PSYNC_TIMEOUT, mPeriodicAdvCallback); BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync( mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT, mPeriodicAdvCallback, null); } catch (IllegalArgumentException ex) { Log.w(TAG, "registerSync:IllegalArgumentException"); Message message = obtainMessage(STOP_SCAN_OFFLOAD); Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +211 −5 Original line number Diff line number Diff line Loading @@ -24,6 +24,10 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_T import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; 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.START_SCAN_OFFLOAD; import static com.android.bluetooth.bass_client.BassClientStateMachine.STOP_SCAN_OFFLOAD; import static com.google.common.truth.Truth.assertThat; Loading @@ -43,6 +47,11 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothLeAudioCodecConfigMetadata; import android.bluetooth.BluetoothLeAudioContentMetadata; import android.bluetooth.BluetoothLeBroadcastChannel; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.bluetooth.BluetoothProfile; import android.bluetooth.le.ScanRecord; import android.content.Intent; Loading @@ -54,6 +63,7 @@ import android.telecom.Log; import androidx.test.filters.MediumTest; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; Loading @@ -68,6 +78,7 @@ 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; Loading @@ -88,16 +99,17 @@ public class BassClientStateMachineTest { private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private BluetoothDevice mTestDevice; @Mock private AdapterService mAdapterService; @Mock private BassClientService mBassClientService; @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); // Get a device for testing mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); Loading Loading @@ -345,7 +357,7 @@ public class BassClientStateMachineTest { // connected ----STOP_SCAN_OFFLOAD---> connectedProcessing --GATT_TXN_PROCESSED--> connected sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD), mBassClientStateMachine.obtainMessage(STOP_SCAN_OFFLOAD), BassClientStateMachine.ConnectedProcessing.class); sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(BassClientStateMachine.GATT_TXN_PROCESSED), Loading Loading @@ -595,6 +607,132 @@ public class BassClientStateMachineTest { 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()); Message noneConnectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED); noneConnectedMsg.obj = BluetoothProfile.STATE_DISCONNECTING; sendMessageAndVerifyTransition(noneConnectedMsg, BassClientStateMachine.Disconnected.class); } @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()); } // TODO: add sendMessage tests for BCAST related messages private void initToDisconnectedState() { allowConnection(true); allowConnectGatt(true); Loading @@ -611,6 +749,15 @@ public class BassClientStateMachineTest { 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 <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) { Mockito.clearInvocations(mBassClientService); mBassClientStateMachine.sendMessage(msg); Loading @@ -621,6 +768,65 @@ public class BassClientStateMachineTest { 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(null) .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; Loading