Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +11 −7 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothLeBroadcastAssistant; import android.bluetooth.IBluetoothLeBroadcastAssistantCallback; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanRecord; Loading Loading @@ -260,8 +259,7 @@ public class BassClientService extends ProfileService { } synchronized (mStateMachines) { for (BassClientStateMachine sm : mStateMachines.values()) { sm.doQuit(); sm.cleanup(); BassObjectsFactory.getInstance().destroyStateMachine(sm); } mStateMachines.clear(); } Loading Loading @@ -369,8 +367,8 @@ public class BassClientService extends ProfileService { return null; } log("Creating a new state machine for " + device); stateMachine = BassClientStateMachine.make(device, this, mStateMachinesThread.getLooper()); stateMachine = BassObjectsFactory.getInstance().makeStateMachine( device, this, mStateMachinesThread.getLooper()); mStateMachines.put(device, stateMachine); return stateMachine; } Loading Loading @@ -620,7 +618,8 @@ public class BassClientService extends ProfileService { Log.e(TAG, "startSearchingForSources: Adapter is NULL"); return; } BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance() .getBluetoothLeScannerWrapper(mBluetoothAdapter); if (scanner == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; Loading Loading @@ -686,7 +685,12 @@ public class BassClientService extends ProfileService { */ public void stopSearchingForSources() { log("stopSearchingForSources"); BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); if (mBluetoothAdapter == null) { Log.e(TAG, "stopSearchingForSources: Adapter is NULL"); return; } BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance() .getBluetoothLeScannerWrapper(mBluetoothAdapter); if (scanner == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +12 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,8 @@ import java.util.Map; import java.util.Scanner; import java.util.stream.IntStream; class BassClientStateMachine extends StateMachine { @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}; Loading Loading @@ -201,6 +202,16 @@ class BassClientStateMachine extends StateMachine { return BassclientSm; } static void destroy(BassClientStateMachine stateMachine) { Log.i(TAG, "destroy"); if (stateMachine == null) { Log.w(TAG, "destroy(), stateMachine is null"); return; } stateMachine.doQuit(); stateMachine.cleanup(); } public void doQuit() { log("doQuit for device " + mDevice); quitNow(); Loading android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.os.Looper; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; /** * Factory class for object initialization to help with unit testing */ public class BassObjectsFactory { private static final String TAG = BassObjectsFactory.class.getSimpleName(); private static BassObjectsFactory sInstance; private static final Object INSTANCE_LOCK = new Object(); private BassObjectsFactory() {} /** * Get the singleton instance of object factory * * @return the singleton instance, guaranteed not null */ public static BassObjectsFactory getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new BassObjectsFactory(); } } return sInstance; } /** * Allow unit tests to substitute BassObjectsFactory with a test instance * * @param objectsFactory a test instance of the BassObjectsFactory */ @VisibleForTesting public static void setInstanceForTesting(BassObjectsFactory objectsFactory) { Utils.enforceInstrumentationTestMode(); synchronized (INSTANCE_LOCK) { Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory); sInstance = objectsFactory; } } /** * Make a {@link BassClientStateMachine} * * @param device the remote device associated with this state machine * @param svc the bass client service * @param looper the thread that the state machine is supposed to run on * @return a state machine that is initialized and started, ready to go */ public BassClientStateMachine makeStateMachine(BluetoothDevice device, BassClientService svc, Looper looper) { return BassClientStateMachine.make(device, svc, looper); } /** * Destroy a state machine * * @param stateMachine to be destroyed. Cannot be used after this call. */ public void destroyStateMachine(BassClientStateMachine stateMachine) { BassClientStateMachine.destroy(stateMachine); } /** * Get a {@link BluetoothLeScannerWrapper} object * * @param adapter bluetooth adapter * @return a bluetooth LE scanner */ public BluetoothLeScannerWrapper getBluetoothLeScannerWrapper(BluetoothAdapter adapter) { return new BluetoothLeScannerWrapper(adapter.getBluetoothLeScanner()); } } android/app/src/com/android/bluetooth/bass_client/BluetoothLeScannerWrapper.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; import java.util.List; /** * Helper class to mock {@link BluetoothLeScanner} which is final. */ public class BluetoothLeScannerWrapper { BluetoothLeScanner mBluetoothLeScanner; BluetoothLeScannerWrapper(BluetoothLeScanner scanner) { mBluetoothLeScanner = scanner; } /** * Starts Bluetooth LE scanning */ public void startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) { mBluetoothLeScanner.startScan(filters, settings, callback); } /** * Stops Bluetooth LE scanning */ public void stopScan(ScanCallback callback) { mBluetoothLeScanner.stopScan(callback); } } android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.notNull; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; import android.content.Context; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.storage.DatabaseManager; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; /** * Tests for {@link BassClientService} */ @MediumTest @RunWith(AndroidJUnit4.class) public class BassClientServiceTest { private static final int MAX_HEADSET_CONNECTIONS = 5; private static final ParcelUuid[] FAKE_SERVICE_UUIDS = {BluetoothUuid.BASS}; private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private final HashMap<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); private Context mTargetContext; private BassClientService mBassClientService; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mCurrentDevice; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Spy private BassObjectsFactory mObjectsFactory = BassObjectsFactory.getInstance(); @Mock private AdapterService mAdapterService; @Mock private DatabaseManager mDatabaseManager; @Mock private BluetoothLeScannerWrapper mBluetoothLeScannerWrapper; @Before public void setUp() throws Exception { mTargetContext = InstrumentationRegistry.getTargetContext(); MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); BassObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(new ParcelUuid[]{BluetoothUuid.BASS}).when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); // This line must be called to make sure relevant objects are initialized properly mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // Mock methods in AdapterService doReturn(FAKE_SERVICE_UUIDS).when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) .getBondState(any(BluetoothDevice.class)); doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); doAnswer(invocation -> { Set<BluetoothDevice> keys = mStateMachines.keySet(); return keys.toArray(new BluetoothDevice[keys.size()]); }).when(mAdapterService).getBondedDevices(); // Mock methods in BassObjectsFactory doAnswer(invocation -> { assertThat(mCurrentDevice).isNotNull(); final BassClientStateMachine stateMachine = mock(BassClientStateMachine.class); mStateMachines.put(mCurrentDevice, stateMachine); return stateMachine; }).when(mObjectsFactory).makeStateMachine(any(), any(), any()); doReturn(mBluetoothLeScannerWrapper).when(mObjectsFactory) .getBluetoothLeScannerWrapper(any()); TestUtils.startService(mServiceRule, BassClientService.class); mBassClientService = BassClientService.getBassClientService(); assertThat(mBassClientService).isNotNull(); } @After public void tearDown() throws Exception { TestUtils.stopService(mServiceRule, BassClientService.class); mBassClientService = BassClientService.getBassClientService(); assertThat(mBassClientService).isNull(); mStateMachines.clear(); mCurrentDevice = null; BassObjectsFactory.setInstanceForTesting(null); TestUtils.clearAdapterService(mAdapterService); } /** * Test to verify that BassClientService can be successfully started */ @Test public void testGetBassClientService() { assertThat(mBassClientService).isEqualTo(BassClientService.getBassClientService()); // Verify default connection and audio states mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mBassClientService.getConnectionState(mCurrentDevice)) .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } /** * Test connecting to a test device. * - service.connect() should return false * - bassClientStateMachine.sendMessage(CONNECT) should be called. */ @Test public void testConnect() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mBassClientService.connect(mCurrentDevice)).isTrue(); verify(mObjectsFactory).makeStateMachine( eq(mCurrentDevice), eq(mBassClientService), any()); BassClientStateMachine stateMachine = mStateMachines.get(mCurrentDevice); assertThat(stateMachine).isNotNull(); verify(stateMachine).sendMessage(BassClientStateMachine.CONNECT); } /** * Test connecting to a null device. * - service.connect() should return false. */ @Test public void testConnect_nullDevice() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); BluetoothDevice nullDevice = null; assertThat(mBassClientService.connect(nullDevice)).isFalse(); } /** * Test connecting to a device when the connection policy is unknown. * - service.connect() should return false. */ @Test public void testConnect_whenConnectionPolicyIsUnknown() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mCurrentDevice).isNotNull(); assertThat(mBassClientService.connect(mCurrentDevice)).isFalse(); } /** * Test whether service.startSearchingForSources() calls BluetoothLeScannerWrapper.startScan(). */ @Test public void testStartSearchingForSources() { List<ScanFilter> scanFilters = new ArrayList<>(); mBassClientService.startSearchingForSources(scanFilters); verify(mBluetoothLeScannerWrapper).startScan(notNull(), notNull(), notNull()); } /** * Test whether service.startSearchingForSources() does not call * BluetoothLeScannerWrapper.startScan() when the scanner instance cannot be achieved. */ @Test public void testStartSearchingForSources_whenScannerIsNull() { doReturn(null).when(mObjectsFactory).getBluetoothLeScannerWrapper(any()); List<ScanFilter> scanFilters = new ArrayList<>(); mBassClientService.startSearchingForSources(scanFilters); verify(mBluetoothLeScannerWrapper, never()).startScan(any(), any(), any()); } } Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +11 −7 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothLeBroadcastAssistant; import android.bluetooth.IBluetoothLeBroadcastAssistantCallback; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanRecord; Loading Loading @@ -260,8 +259,7 @@ public class BassClientService extends ProfileService { } synchronized (mStateMachines) { for (BassClientStateMachine sm : mStateMachines.values()) { sm.doQuit(); sm.cleanup(); BassObjectsFactory.getInstance().destroyStateMachine(sm); } mStateMachines.clear(); } Loading Loading @@ -369,8 +367,8 @@ public class BassClientService extends ProfileService { return null; } log("Creating a new state machine for " + device); stateMachine = BassClientStateMachine.make(device, this, mStateMachinesThread.getLooper()); stateMachine = BassObjectsFactory.getInstance().makeStateMachine( device, this, mStateMachinesThread.getLooper()); mStateMachines.put(device, stateMachine); return stateMachine; } Loading Loading @@ -620,7 +618,8 @@ public class BassClientService extends ProfileService { Log.e(TAG, "startSearchingForSources: Adapter is NULL"); return; } BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance() .getBluetoothLeScannerWrapper(mBluetoothAdapter); if (scanner == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; Loading Loading @@ -686,7 +685,12 @@ public class BassClientService extends ProfileService { */ public void stopSearchingForSources() { log("stopSearchingForSources"); BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); if (mBluetoothAdapter == null) { Log.e(TAG, "stopSearchingForSources: Adapter is NULL"); return; } BluetoothLeScannerWrapper scanner = BassObjectsFactory.getInstance() .getBluetoothLeScannerWrapper(mBluetoothAdapter); if (scanner == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +12 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,8 @@ import java.util.Map; import java.util.Scanner; import java.util.stream.IntStream; class BassClientStateMachine extends StateMachine { @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}; Loading Loading @@ -201,6 +202,16 @@ class BassClientStateMachine extends StateMachine { return BassclientSm; } static void destroy(BassClientStateMachine stateMachine) { Log.i(TAG, "destroy"); if (stateMachine == null) { Log.w(TAG, "destroy(), stateMachine is null"); return; } stateMachine.doQuit(); stateMachine.cleanup(); } public void doQuit() { log("doQuit for device " + mDevice); quitNow(); Loading
android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.os.Looper; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.VisibleForTesting; /** * Factory class for object initialization to help with unit testing */ public class BassObjectsFactory { private static final String TAG = BassObjectsFactory.class.getSimpleName(); private static BassObjectsFactory sInstance; private static final Object INSTANCE_LOCK = new Object(); private BassObjectsFactory() {} /** * Get the singleton instance of object factory * * @return the singleton instance, guaranteed not null */ public static BassObjectsFactory getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new BassObjectsFactory(); } } return sInstance; } /** * Allow unit tests to substitute BassObjectsFactory with a test instance * * @param objectsFactory a test instance of the BassObjectsFactory */ @VisibleForTesting public static void setInstanceForTesting(BassObjectsFactory objectsFactory) { Utils.enforceInstrumentationTestMode(); synchronized (INSTANCE_LOCK) { Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory); sInstance = objectsFactory; } } /** * Make a {@link BassClientStateMachine} * * @param device the remote device associated with this state machine * @param svc the bass client service * @param looper the thread that the state machine is supposed to run on * @return a state machine that is initialized and started, ready to go */ public BassClientStateMachine makeStateMachine(BluetoothDevice device, BassClientService svc, Looper looper) { return BassClientStateMachine.make(device, svc, looper); } /** * Destroy a state machine * * @param stateMachine to be destroyed. Cannot be used after this call. */ public void destroyStateMachine(BassClientStateMachine stateMachine) { BassClientStateMachine.destroy(stateMachine); } /** * Get a {@link BluetoothLeScannerWrapper} object * * @param adapter bluetooth adapter * @return a bluetooth LE scanner */ public BluetoothLeScannerWrapper getBluetoothLeScannerWrapper(BluetoothAdapter adapter) { return new BluetoothLeScannerWrapper(adapter.getBluetoothLeScanner()); } }
android/app/src/com/android/bluetooth/bass_client/BluetoothLeScannerWrapper.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; import java.util.List; /** * Helper class to mock {@link BluetoothLeScanner} which is final. */ public class BluetoothLeScannerWrapper { BluetoothLeScanner mBluetoothLeScanner; BluetoothLeScannerWrapper(BluetoothLeScanner scanner) { mBluetoothLeScanner = scanner; } /** * Starts Bluetooth LE scanning */ public void startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) { mBluetoothLeScanner.startScan(filters, settings, callback); } /** * Stops Bluetooth LE scanning */ public void stopScan(ScanCallback callback) { mBluetoothLeScanner.stopScan(callback); } }
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 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 com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.notNull; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; import android.content.Context; import android.os.ParcelUuid; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.storage.DatabaseManager; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; /** * Tests for {@link BassClientService} */ @MediumTest @RunWith(AndroidJUnit4.class) public class BassClientServiceTest { private static final int MAX_HEADSET_CONNECTIONS = 5; private static final ParcelUuid[] FAKE_SERVICE_UUIDS = {BluetoothUuid.BASS}; private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private final HashMap<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); private Context mTargetContext; private BassClientService mBassClientService; private BluetoothAdapter mBluetoothAdapter; private BluetoothDevice mCurrentDevice; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); @Spy private BassObjectsFactory mObjectsFactory = BassObjectsFactory.getInstance(); @Mock private AdapterService mAdapterService; @Mock private DatabaseManager mDatabaseManager; @Mock private BluetoothLeScannerWrapper mBluetoothLeScannerWrapper; @Before public void setUp() throws Exception { mTargetContext = InstrumentationRegistry.getTargetContext(); MockitoAnnotations.initMocks(this); TestUtils.setAdapterService(mAdapterService); BassObjectsFactory.setInstanceForTesting(mObjectsFactory); doReturn(new ParcelUuid[]{BluetoothUuid.BASS}).when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); // This line must be called to make sure relevant objects are initialized properly mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // Mock methods in AdapterService doReturn(FAKE_SERVICE_UUIDS).when(mAdapterService) .getRemoteUuids(any(BluetoothDevice.class)); doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) .getBondState(any(BluetoothDevice.class)); doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); doAnswer(invocation -> { Set<BluetoothDevice> keys = mStateMachines.keySet(); return keys.toArray(new BluetoothDevice[keys.size()]); }).when(mAdapterService).getBondedDevices(); // Mock methods in BassObjectsFactory doAnswer(invocation -> { assertThat(mCurrentDevice).isNotNull(); final BassClientStateMachine stateMachine = mock(BassClientStateMachine.class); mStateMachines.put(mCurrentDevice, stateMachine); return stateMachine; }).when(mObjectsFactory).makeStateMachine(any(), any(), any()); doReturn(mBluetoothLeScannerWrapper).when(mObjectsFactory) .getBluetoothLeScannerWrapper(any()); TestUtils.startService(mServiceRule, BassClientService.class); mBassClientService = BassClientService.getBassClientService(); assertThat(mBassClientService).isNotNull(); } @After public void tearDown() throws Exception { TestUtils.stopService(mServiceRule, BassClientService.class); mBassClientService = BassClientService.getBassClientService(); assertThat(mBassClientService).isNull(); mStateMachines.clear(); mCurrentDevice = null; BassObjectsFactory.setInstanceForTesting(null); TestUtils.clearAdapterService(mAdapterService); } /** * Test to verify that BassClientService can be successfully started */ @Test public void testGetBassClientService() { assertThat(mBassClientService).isEqualTo(BassClientService.getBassClientService()); // Verify default connection and audio states mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mBassClientService.getConnectionState(mCurrentDevice)) .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); } /** * Test connecting to a test device. * - service.connect() should return false * - bassClientStateMachine.sendMessage(CONNECT) should be called. */ @Test public void testConnect() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mBassClientService.connect(mCurrentDevice)).isTrue(); verify(mObjectsFactory).makeStateMachine( eq(mCurrentDevice), eq(mBassClientService), any()); BassClientStateMachine stateMachine = mStateMachines.get(mCurrentDevice); assertThat(stateMachine).isNotNull(); verify(stateMachine).sendMessage(BassClientStateMachine.CONNECT); } /** * Test connecting to a null device. * - service.connect() should return false. */ @Test public void testConnect_nullDevice() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); BluetoothDevice nullDevice = null; assertThat(mBassClientService.connect(nullDevice)).isFalse(); } /** * Test connecting to a device when the connection policy is unknown. * - service.connect() should return false. */ @Test public void testConnect_whenConnectionPolicyIsUnknown() { when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); mCurrentDevice = TestUtils.getTestDevice(mBluetoothAdapter, 0); assertThat(mCurrentDevice).isNotNull(); assertThat(mBassClientService.connect(mCurrentDevice)).isFalse(); } /** * Test whether service.startSearchingForSources() calls BluetoothLeScannerWrapper.startScan(). */ @Test public void testStartSearchingForSources() { List<ScanFilter> scanFilters = new ArrayList<>(); mBassClientService.startSearchingForSources(scanFilters); verify(mBluetoothLeScannerWrapper).startScan(notNull(), notNull(), notNull()); } /** * Test whether service.startSearchingForSources() does not call * BluetoothLeScannerWrapper.startScan() when the scanner instance cannot be achieved. */ @Test public void testStartSearchingForSources_whenScannerIsNull() { doReturn(null).when(mObjectsFactory).getBluetoothLeScannerWrapper(any()); List<ScanFilter> scanFilters = new ArrayList<>(); mBassClientService.startSearchingForSources(scanFilters); verify(mBluetoothLeScannerWrapper, never()).startScan(any(), any(), any()); } }