Loading src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamScanHelper.java +82 −3 Original line number Diff line number Diff line Loading @@ -22,14 +22,23 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_ON; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_TURNING_OFF; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_TURNING_ON; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_WAITING_TO_RESTART_WITH_NO_FILTER; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID; import static java.util.Collections.emptyList; import android.annotation.NonNull; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.le.ScanFilter; import android.os.ParcelUuid; import android.util.Log; import androidx.annotation.NonNull; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading @@ -47,10 +56,14 @@ public class AudioStreamScanHelper implements STATE_OFF, STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF STATE_TURNING_OFF, STATE_WAITING_TO_RESTART_WITH_NO_FILTER, } private static final String TAG = "AudioStreamScanHelper"; private static final ParcelUuid BAAS_UUID = ParcelUuid.fromString( "00001852-0000-1000-8000-00805F9B34FB"); private static final String UNSET_DEVICE_ADDR = "FF:FF:FF:FF:FF:FF"; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final Consumer<Boolean> mScanStateChangedListener; private final @NonNull Executor mExecutor; Loading Loading @@ -82,6 +95,68 @@ public class AudioStreamScanHelper implements }); } /** * Starts the scanning process for one specific audio stream when info in metadata is available. * This method will do nothing if scanning is already started or in the process of starting. */ public void startScanningWithFilter(@NonNull BluetoothLeBroadcastMetadata metadata) { mExecutor.execute(() -> { if (mState == STATE_ON || mState == STATE_TURNING_ON) { Log.d(TAG, "startScanningWithFilter() : do nothing, state = " + mState); return; } boolean useFilter = false; ScanFilter.Builder builder = new ScanFilter.Builder(); if (metadata.getBroadcastId() != UNSET_BROADCAST_ID) { byte[] broadcastIdBytes = createBroadcastIdBytes(metadata.getBroadcastId()); byte[] serviceDataMask = new byte[broadcastIdBytes.length]; Arrays.fill(serviceDataMask, (byte) 0xFF); builder.setServiceData(BAAS_UUID, broadcastIdBytes, serviceDataMask); useFilter = true; } if (metadata.getSourceDevice() != null && !Objects.equals( metadata.getSourceDevice().getAddress(), UNSET_DEVICE_ADDR)) { builder.setDeviceAddress(metadata.getSourceDevice().getAddress()); useFilter = true; } var filter = builder.build(); if (mLeBroadcastAssistant != null) { Log.d(TAG, "startScanningWithFilter() : scanFilter applied : " + filter); mLeBroadcastAssistant.startSearchingForSources( useFilter ? List.of(filter) : emptyList()); setState(STATE_TURNING_ON); } }); } /** * Restarts the scanning process without scan filter. * This method will do nothing if scanning is not started or in the process of starting. */ public void restartScanningWithoutFilter() { mExecutor.execute(() -> { if (mState != STATE_ON && mState != STATE_TURNING_ON) { Log.d(TAG, "restartScanningWithoutFilter() : do nothing, state = " + mState); return; } if (mLeBroadcastAssistant != null) { Log.d(TAG, "restartScanningWithoutFilter() : stop scanning"); mLeBroadcastAssistant.stopSearchingForSources(); setState(STATE_WAITING_TO_RESTART_WITH_NO_FILTER); } }); } private static byte[] createBroadcastIdBytes(int broadcastId) { byte[] byteArray = new byte[3]; byteArray[0] = (byte) (broadcastId & 0xFF); byteArray[1] = (byte) ((broadcastId >> 8) & 0xFF); byteArray[2] = (byte) ((broadcastId >> 16) & 0xFF); return byteArray; } /** * Stops the ongoing scanning process for audio stream sources. * This method will do nothing if scanning is already off or in the process of stopping. Loading Loading @@ -120,7 +195,11 @@ public class AudioStreamScanHelper implements public void scanningStopped() { mExecutor.execute(() -> { Log.d(TAG, "scanningStopped()"); if (mState == STATE_WAITING_TO_RESTART_WITH_NO_FILTER) { startScanning(); } else { setState(STATE_OFF); } }); } Loading tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamScanHelperTest.java +148 −0 Original line number Diff line number Diff line Loading @@ -18,17 +18,26 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static android.bluetooth.BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.le.ScanFilter; import android.content.Context; import android.os.Looper; import android.os.ParcelUuid; import androidx.test.core.app.ApplicationProvider; Loading @@ -44,6 +53,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import java.util.List; import java.util.function.Consumer; @RunWith(RobolectricTestRunner.class) Loading Loading @@ -195,4 +205,142 @@ public class AudioStreamScanHelperTest { verify(mScanStateChangedListener, times(2)).accept(captor.capture()); assertThat(captor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithBroadcastId_startWithFilter() { BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(123); when(metadata.getSourceDevice()).thenReturn(null); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getServiceDataUuid()).isEqualTo( ParcelUuid.fromString("00001852-0000-1000-8000-00805F9B34FB")); assertThat(filter.getServiceData()).isEqualTo(new byte[]{0x7B, 0x00, 0x00}); assertThat(filter.getServiceDataMask()).isEqualTo( new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); assertThat(filter.getDeviceAddress()).isNull(); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithDeviceAddress_startWithFilter() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAddress()).thenReturn("AA:BB:CC:DD:EE:FF"); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(UNSET_BROADCAST_ID); when(metadata.getSourceDevice()).thenReturn(device); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getDeviceAddress()).isEqualTo("AA:BB:CC:DD:EE:FF"); assertThat(filter.getServiceDataUuid()).isNull(); assertThat(filter.getServiceData()).isNull(); assertThat(filter.getServiceDataMask()).isNull(); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithBothFilters_startWithFilter() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAddress()).thenReturn("AA:BB:CC:DD:EE:FF"); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(456); when(metadata.getSourceDevice()).thenReturn(device); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getServiceDataUuid()).isEqualTo( ParcelUuid.fromString("00001852-0000-1000-8000-00805F9B34FB")); assertThat(filter.getServiceData()).isEqualTo(new byte[]{(byte) 0xC8, 0x01, 0x00}); assertThat(filter.getServiceDataMask()).isEqualTo( new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); assertThat(filter.getDeviceAddress()).isEqualTo("AA:BB:CC:DD:EE:FF"); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_scanningAlreadyOn_doesNothing() { mScanHelper.startScanning(); shadowOf(Looper.getMainLooper()).idle(); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); // Trigger only once verify(mLeBroadcastAssistant).startSearchingForSources(any()); verify(mScanStateChangedListener).accept(anyBoolean()); } @Test public void restartScanningWithoutFilter_scanningOn_stopsAndRestartsScanningWithoutFilter() { mScanHelper.scanningStarted(); shadowOf(Looper.getMainLooper()).idle(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant).stopSearchingForSources(); shadowOf(Looper.getMainLooper()).idle(); mScanHelper.scanningStopped(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant).startSearchingForSources(any()); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener, times(3)).accept(stateCaptor.capture()); var values = stateCaptor.getAllValues(); assertThat(values.get(0)).isTrue(); assertThat(values.get(1)).isFalse(); assertThat(values.get(2)).isTrue(); } @Test public void restartScanningWithoutFilter_scanningOff_doesNothing() { mScanHelper.stopScanning(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant, never()).stopSearchingForSources(); verify(mLeBroadcastAssistant, never()).startSearchingForSources(any()); verify(mScanStateChangedListener, never()).accept(anyBoolean()); } @Test public void restartScanningWithoutFilter_scanningTurningOff_doesNothing() { mScanHelper.stopScanning(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant, never()).stopSearchingForSources(); verify(mLeBroadcastAssistant, never()).startSearchingForSources(any()); verify(mScanStateChangedListener, never()).accept(anyBoolean()); } } Loading
src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamScanHelper.java +82 −3 Original line number Diff line number Diff line Loading @@ -22,14 +22,23 @@ import static com.android.settings.connecteddevice.audiosharing.audiostreams.Aud import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_ON; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_TURNING_OFF; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_TURNING_ON; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamScanHelper.State.STATE_WAITING_TO_RESTART_WITH_NO_FILTER; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID; import static java.util.Collections.emptyList; import android.annotation.NonNull; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.le.ScanFilter; import android.os.ParcelUuid; import android.util.Log; import androidx.annotation.NonNull; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading @@ -47,10 +56,14 @@ public class AudioStreamScanHelper implements STATE_OFF, STATE_TURNING_ON, STATE_ON, STATE_TURNING_OFF STATE_TURNING_OFF, STATE_WAITING_TO_RESTART_WITH_NO_FILTER, } private static final String TAG = "AudioStreamScanHelper"; private static final ParcelUuid BAAS_UUID = ParcelUuid.fromString( "00001852-0000-1000-8000-00805F9B34FB"); private static final String UNSET_DEVICE_ADDR = "FF:FF:FF:FF:FF:FF"; private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; private final Consumer<Boolean> mScanStateChangedListener; private final @NonNull Executor mExecutor; Loading Loading @@ -82,6 +95,68 @@ public class AudioStreamScanHelper implements }); } /** * Starts the scanning process for one specific audio stream when info in metadata is available. * This method will do nothing if scanning is already started or in the process of starting. */ public void startScanningWithFilter(@NonNull BluetoothLeBroadcastMetadata metadata) { mExecutor.execute(() -> { if (mState == STATE_ON || mState == STATE_TURNING_ON) { Log.d(TAG, "startScanningWithFilter() : do nothing, state = " + mState); return; } boolean useFilter = false; ScanFilter.Builder builder = new ScanFilter.Builder(); if (metadata.getBroadcastId() != UNSET_BROADCAST_ID) { byte[] broadcastIdBytes = createBroadcastIdBytes(metadata.getBroadcastId()); byte[] serviceDataMask = new byte[broadcastIdBytes.length]; Arrays.fill(serviceDataMask, (byte) 0xFF); builder.setServiceData(BAAS_UUID, broadcastIdBytes, serviceDataMask); useFilter = true; } if (metadata.getSourceDevice() != null && !Objects.equals( metadata.getSourceDevice().getAddress(), UNSET_DEVICE_ADDR)) { builder.setDeviceAddress(metadata.getSourceDevice().getAddress()); useFilter = true; } var filter = builder.build(); if (mLeBroadcastAssistant != null) { Log.d(TAG, "startScanningWithFilter() : scanFilter applied : " + filter); mLeBroadcastAssistant.startSearchingForSources( useFilter ? List.of(filter) : emptyList()); setState(STATE_TURNING_ON); } }); } /** * Restarts the scanning process without scan filter. * This method will do nothing if scanning is not started or in the process of starting. */ public void restartScanningWithoutFilter() { mExecutor.execute(() -> { if (mState != STATE_ON && mState != STATE_TURNING_ON) { Log.d(TAG, "restartScanningWithoutFilter() : do nothing, state = " + mState); return; } if (mLeBroadcastAssistant != null) { Log.d(TAG, "restartScanningWithoutFilter() : stop scanning"); mLeBroadcastAssistant.stopSearchingForSources(); setState(STATE_WAITING_TO_RESTART_WITH_NO_FILTER); } }); } private static byte[] createBroadcastIdBytes(int broadcastId) { byte[] byteArray = new byte[3]; byteArray[0] = (byte) (broadcastId & 0xFF); byteArray[1] = (byte) ((broadcastId >> 8) & 0xFF); byteArray[2] = (byte) ((broadcastId >> 16) & 0xFF); return byteArray; } /** * Stops the ongoing scanning process for audio stream sources. * This method will do nothing if scanning is already off or in the process of stopping. Loading Loading @@ -120,7 +195,11 @@ public class AudioStreamScanHelper implements public void scanningStopped() { mExecutor.execute(() -> { Log.d(TAG, "scanningStopped()"); if (mState == STATE_WAITING_TO_RESTART_WITH_NO_FILTER) { startScanning(); } else { setState(STATE_OFF); } }); } Loading
tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamScanHelperTest.java +148 −0 Original line number Diff line number Diff line Loading @@ -18,17 +18,26 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams; import static android.bluetooth.BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE; import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.le.ScanFilter; import android.content.Context; import android.os.Looper; import android.os.ParcelUuid; import androidx.test.core.app.ApplicationProvider; Loading @@ -44,6 +53,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; import java.util.List; import java.util.function.Consumer; @RunWith(RobolectricTestRunner.class) Loading Loading @@ -195,4 +205,142 @@ public class AudioStreamScanHelperTest { verify(mScanStateChangedListener, times(2)).accept(captor.capture()); assertThat(captor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithBroadcastId_startWithFilter() { BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(123); when(metadata.getSourceDevice()).thenReturn(null); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getServiceDataUuid()).isEqualTo( ParcelUuid.fromString("00001852-0000-1000-8000-00805F9B34FB")); assertThat(filter.getServiceData()).isEqualTo(new byte[]{0x7B, 0x00, 0x00}); assertThat(filter.getServiceDataMask()).isEqualTo( new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); assertThat(filter.getDeviceAddress()).isNull(); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithDeviceAddress_startWithFilter() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAddress()).thenReturn("AA:BB:CC:DD:EE:FF"); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(UNSET_BROADCAST_ID); when(metadata.getSourceDevice()).thenReturn(device); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getDeviceAddress()).isEqualTo("AA:BB:CC:DD:EE:FF"); assertThat(filter.getServiceDataUuid()).isNull(); assertThat(filter.getServiceData()).isNull(); assertThat(filter.getServiceDataMask()).isNull(); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_assistantNotNullWithBothFilters_startWithFilter() { BluetoothDevice device = mock(BluetoothDevice.class); when(device.getAddress()).thenReturn("AA:BB:CC:DD:EE:FF"); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); when(metadata.getBroadcastId()).thenReturn(456); when(metadata.getSourceDevice()).thenReturn(device); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); ArgumentCaptor<List<ScanFilter>> filterCaptor = ArgumentCaptor.forClass(List.class); verify(mLeBroadcastAssistant).startSearchingForSources(filterCaptor.capture()); assertThat(filterCaptor.getValue()).hasSize(1); ScanFilter filter = filterCaptor.getValue().getFirst(); assertThat(filter.getServiceDataUuid()).isEqualTo( ParcelUuid.fromString("00001852-0000-1000-8000-00805F9B34FB")); assertThat(filter.getServiceData()).isEqualTo(new byte[]{(byte) 0xC8, 0x01, 0x00}); assertThat(filter.getServiceDataMask()).isEqualTo( new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}); assertThat(filter.getDeviceAddress()).isEqualTo("AA:BB:CC:DD:EE:FF"); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener).accept(stateCaptor.capture()); assertThat(stateCaptor.getValue()).isTrue(); } @Test public void startScanningWithFilter_scanningAlreadyOn_doesNothing() { mScanHelper.startScanning(); shadowOf(Looper.getMainLooper()).idle(); BluetoothLeBroadcastMetadata metadata = mock(BluetoothLeBroadcastMetadata.class); mScanHelper.startScanningWithFilter(metadata); shadowOf(Looper.getMainLooper()).idle(); // Trigger only once verify(mLeBroadcastAssistant).startSearchingForSources(any()); verify(mScanStateChangedListener).accept(anyBoolean()); } @Test public void restartScanningWithoutFilter_scanningOn_stopsAndRestartsScanningWithoutFilter() { mScanHelper.scanningStarted(); shadowOf(Looper.getMainLooper()).idle(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant).stopSearchingForSources(); shadowOf(Looper.getMainLooper()).idle(); mScanHelper.scanningStopped(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant).startSearchingForSources(any()); ArgumentCaptor<Boolean> stateCaptor = ArgumentCaptor.forClass(Boolean.class); verify(mScanStateChangedListener, times(3)).accept(stateCaptor.capture()); var values = stateCaptor.getAllValues(); assertThat(values.get(0)).isTrue(); assertThat(values.get(1)).isFalse(); assertThat(values.get(2)).isTrue(); } @Test public void restartScanningWithoutFilter_scanningOff_doesNothing() { mScanHelper.stopScanning(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant, never()).stopSearchingForSources(); verify(mLeBroadcastAssistant, never()).startSearchingForSources(any()); verify(mScanStateChangedListener, never()).accept(anyBoolean()); } @Test public void restartScanningWithoutFilter_scanningTurningOff_doesNothing() { mScanHelper.stopScanning(); mScanHelper.restartScanningWithoutFilter(); shadowOf(Looper.getMainLooper()).idle(); verify(mLeBroadcastAssistant, never()).stopSearchingForSources(); verify(mLeBroadcastAssistant, never()).startSearchingForSources(any()); verify(mScanStateChangedListener, never()).accept(anyBoolean()); } }