diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java index 6441d170c44ee3fa6b726363890b2fece90e1520..9873eedd20449967cbf155c13259925118eead83 100644 --- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java +++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java @@ -239,6 +239,11 @@ public class BluetoothMethodProxy { manager.registerSync(scanResult, skip, timeout, callback, handler); } + /** Proxies {@link PeriodicAdvertisingManager#unregisterSync(PeriodicAdvertisingCallback)}. */ + public void periodicAdvertisingManagerUnregisterSync( + PeriodicAdvertisingManager manager, PeriodicAdvertisingCallback callback) { + manager.unregisterSync(callback); + } /** * Proxies {@link PeriodicAdvertisingManager#transferSync}. */ diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientPeriodicAdvertisingManager.java b/android/app/src/com/android/bluetooth/bass_client/BassClientPeriodicAdvertisingManager.java new file mode 100644 index 0000000000000000000000000000000000000000..0e8890cdada4da8e24dd181348ceb0f862e32cf4 --- /dev/null +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientPeriodicAdvertisingManager.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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.le.PeriodicAdvertisingManager; +import android.util.Log; + +/** Bass Client Periodic Advertising object handler */ +class BassClientPeriodicAdvertisingManager { + private static final String TAG = "BassClientPeriodicAdvertisingManager"; + + private static PeriodicAdvertisingManager sPeriodicAdvertisingManager; + + private BassClientPeriodicAdvertisingManager() {} + + /** + * Return true if initialization of Periodic Advertising Manager instance on Default Bluetooth + * Adapter is successful. + */ + public static boolean initializePeriodicAdvertisingManagerOnDefaultAdapter() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + + if (sPeriodicAdvertisingManager != null) { + Log.w(TAG, "Periodic Advertising Manager already initialized - re-initializing"); + } + + if (adapter == null) { + Log.e(TAG, "Failed to get Default Bluetooth Adapter"); + return false; + } + + sPeriodicAdvertisingManager = adapter.getPeriodicAdvertisingManager(); + + if (sPeriodicAdvertisingManager == null) { + Log.e(TAG, "Failed to get Default Periodic Advertising Manager"); + return false; + } + + Log.d(TAG, "BassClientPeriodicAdvertisingManager initialized"); + + return true; + } + + public static synchronized PeriodicAdvertisingManager getPeriodicAdvertisingManager() { + if (sPeriodicAdvertisingManager == null) { + Log.e( + TAG, + "getPeriodicAdvertisingManager: Periodic Advertising Manager is not " + + "initialized"); + return null; + } + + return sPeriodicAdvertisingManager; + } + + public static void clearPeriodicAdvertisingManager() { + if (sPeriodicAdvertisingManager == null) { + Log.e( + TAG, + "clearPeriodicAdvertisingManager: Periodic Advertising Manager is not " + + "initialized"); + return; + } + + Log.d(TAG, "BassClientPeriodicAdvertisingManager cleared"); + + sPeriodicAdvertisingManager = null; + } +} diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 3f5d5f5864850b473fdcd76f6db6cdf8d9acd3f4..03385dc457c78fcf79043e9bde4e333573e73ffa 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -717,6 +717,12 @@ public class BassClientService extends ProfileService { if (mGroupManagedSources.containsKey(sink) && mGroupManagedSources.get(sink).contains(sourceId)) { BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); + if (stateMachine == null) { + Log.e(TAG, "Can't get state machine for device: " + sink); + return new Pair<BluetoothLeBroadcastMetadata, Map<BluetoothDevice, Integer>>( + null, null); + } + BluetoothLeBroadcastMetadata metadata = stateMachine.getCurrentBroadcastMetadata(sourceId); if (metadata != null) { @@ -867,8 +873,15 @@ public class BassClientService extends ProfileService { stateMachine = BassObjectsFactory.getInstance() .makeStateMachine( - device, this, mStateMachinesThread.getLooper(), mFeatureFlags); - mStateMachines.put(device, stateMachine); + device, + this, + mAdapterService, + mStateMachinesThread.getLooper(), + mFeatureFlags); + if (stateMachine != null) { + mStateMachines.put(device, stateMachine); + } + return stateMachine; } } @@ -1050,6 +1063,11 @@ public class BassClientService extends ProfileService { } synchronized (mStateMachines) { BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + if (stateMachine == null) { + Log.e(TAG, "Can't get state machine for device: " + device); + return false; + } + stateMachine.sendMessage(BassClientStateMachine.CONNECT); } return true; @@ -1071,6 +1089,11 @@ public class BassClientService extends ProfileService { } synchronized (mStateMachines) { BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + if (stateMachine == null) { + Log.e(TAG, "Can't get state machine for device: " + device); + return false; + } + stateMachine.sendMessage(BassClientStateMachine.DISCONNECT); } return true; @@ -1393,6 +1416,10 @@ public class BassClientService extends ProfileService { // removing the 1st synced source before proceeding to add new synchronized (mStateMachines) { BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); + if (stateMachine == null) { + Log.e(TAG, "Can't get state machine for device: " + sink); + return; + } Message message = stateMachine.obtainMessage(BassClientStateMachine.REACHED_MAX_SOURCE_LIMIT); message.arg1 = syncHandle; @@ -1404,6 +1431,10 @@ public class BassClientService extends ProfileService { sEventLogger.logd(TAG, "Select Broadcast Source"); BassClientStateMachine stateMachine = getOrCreateStateMachine(sink); + if (stateMachine == null) { + Log.e(TAG, "Can't get state machine for device: " + sink); + return; + } Message message = stateMachine.obtainMessage( BassClientStateMachine.SELECT_BCAST_SOURCE); message.obj = result; diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java index ded86006cca09411d6655574eaaa38813f928f0c..9484b9c6fd2a1723277589e2c2ef27703a437a73 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java @@ -19,7 +19,6 @@ package com.android.bluetooth.bass_client; import static android.Manifest.permission.BLUETOOTH_CONNECT; import android.annotation.Nullable; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; @@ -38,7 +37,6 @@ import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUtils; import android.bluetooth.BluetoothUtils.TypeValueEntry; import android.bluetooth.le.PeriodicAdvertisingCallback; -import android.bluetooth.le.PeriodicAdvertisingManager; import android.bluetooth.le.PeriodicAdvertisingReport; import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; @@ -53,8 +51,8 @@ import android.util.Pair; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; -import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.flags.FeatureFlags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; @@ -148,6 +146,7 @@ public class BassClientStateMachine extends StateMachine { boolean mDiscoveryInitiated = false; @VisibleForTesting BassClientService mService; + AdapterService mAdapterService; @VisibleForTesting BluetoothGattCharacteristic mBroadcastScanControlPoint; private final Map<Integer, Boolean> mFirstTimeBisDiscoveryMap; @@ -155,9 +154,6 @@ public class BassClientStateMachine extends StateMachine { private ScanResult mScanRes = null; @VisibleForTesting int mNumOfBroadcastReceiverStates = 0; - private BluetoothAdapter mBluetoothAdapter = - BluetoothAdapter.getDefaultAdapter(); - private ServiceFactory mFactory = new ServiceFactory(); @VisibleForTesting int mPendingOperation = -1; @VisibleForTesting @@ -168,8 +164,6 @@ public class BassClientStateMachine extends StateMachine { @VisibleForTesting boolean mSetBroadcastCodePending = false; private final Map<Integer, Boolean> mPendingRemove = new HashMap(); - // Psync and PAST interfaces - private PeriodicAdvertisingManager mPeriodicAdvManager; @VisibleForTesting boolean mAutoTriggered = false; private boolean mDefNoPAS = false; @@ -190,12 +184,14 @@ public class BassClientStateMachine extends StateMachine { BassClientStateMachine( BluetoothDevice device, BassClientService svc, + AdapterService adapterService, Looper looper, int connectTimeoutMs, FeatureFlags featureFlags) { super(TAG + "(" + device.toString() + ")", looper); mDevice = device; mService = svc; + mAdapterService = adapterService; mConnectTimeoutMs = connectTimeoutMs; mFeatureFlags = Objects.requireNonNull(featureFlags, "Feature Flags cannot be null"); addState(mDisconnected); @@ -203,11 +199,6 @@ public class BassClientStateMachine extends StateMachine { addState(mConnecting); addState(mConnectedProcessing); setInitialState(mDisconnected); - // PSYNC and PAST instances - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mBluetoothAdapter != null) { - mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager(); - } mFirstTimeBisDiscoveryMap = new HashMap<Integer, Boolean>(); long token = Binder.clearCallingIdentity(); mIsAllowedList = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, @@ -222,12 +213,25 @@ public class BassClientStateMachine extends StateMachine { static BassClientStateMachine make( BluetoothDevice device, BassClientService svc, + AdapterService adapterService, Looper looper, FeatureFlags featureFlags) { Log.d(TAG, "make for device " + device); + + if (!BassClientPeriodicAdvertisingManager + .initializePeriodicAdvertisingManagerOnDefaultAdapter()) { + Log.e(TAG, "Failed to initialize Periodic Advertising Manager on Default Adapter"); + return null; + } + BassClientStateMachine BassclientSm = new BassClientStateMachine( - device, svc, looper, BassConstants.CONNECT_TIMEOUT_MS, featureFlags); + device, + svc, + adapterService, + looper, + BassConstants.CONNECT_TIMEOUT_MS, + featureFlags); BassclientSm.start(); return BassclientSm; } @@ -455,9 +459,15 @@ public class BassClientStateMachine extends StateMachine { int tempHandle = BassConstants.INVALID_SYNC_HANDLE; mPeriodicAdvCallbacksMap.put(tempHandle, paCb); try { - BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync( - mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT, - paCb, null); + BluetoothMethodProxy.getInstance() + .periodicAdvertisingManagerRegisterSync( + BassClientPeriodicAdvertisingManager + .getPeriodicAdvertisingManager(), + scanRes, + 0, + BassConstants.PSYNC_TIMEOUT, + paCb, + null); } catch (IllegalArgumentException ex) { Log.w(TAG, "registerSync:IllegalArgumentException"); Message message = obtainMessage(STOP_SCAN_OFFLOAD); @@ -522,7 +532,11 @@ public class BassClientStateMachine extends StateMachine { if (syncHandle != BassConstants.INVALID_SYNC_HANDLE && mPeriodicAdvCallbacksMap.containsKey(syncHandle)) { try { - mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallbacksMap.get(syncHandle)); + BluetoothMethodProxy.getInstance() + .periodicAdvertisingManagerUnregisterSync( + BassClientPeriodicAdvertisingManager + .getPeriodicAdvertisingManager(), + mPeriodicAdvCallbacksMap.get(syncHandle)); } catch (IllegalArgumentException ex) { Log.w(TAG, "unregisterSync:IllegalArgumentException"); return false; @@ -686,7 +700,11 @@ public class BassClientStateMachine extends StateMachine { + "serviceData" + serviceData); BluetoothMethodProxy.getInstance() .periodicAdvertisingManagerTransferSync( - mPeriodicAdvManager, mDevice, serviceData, syncHandle); + BassClientPeriodicAdvertisingManager + .getPeriodicAdvertisingManager(), + mDevice, + serviceData, + syncHandle); } } else { BluetoothLeBroadcastMetadata currentMetadata = @@ -701,9 +719,14 @@ public class BassClientStateMachine extends StateMachine { log("Initiate local broadcast PAST for: " + mDevice + ", advSID/Handle: " + advHandle + ", serviceData: " + serviceData); - BluetoothMethodProxy.getInstance().periodicAdvertisingManagerTransferSetInfo( - mPeriodicAdvManager, mDevice, serviceData, advHandle, - mLocalPeriodicAdvCallback); + BluetoothMethodProxy.getInstance() + .periodicAdvertisingManagerTransferSetInfo( + BassClientPeriodicAdvertisingManager + .getPeriodicAdvertisingManager(), + mDevice, + serviceData, + advHandle, + mLocalPeriodicAdvCallback); } else { Log.e(TAG, "There is no valid sync handle for this Source"); } @@ -744,25 +767,28 @@ public class BassClientStateMachine extends StateMachine { } log("processBroadcastReceiverState: receiverState length: " + receiverState.length); - BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothLeBroadcastReceiveState recvState = null; if (receiverState.length == 0 || isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length - 1))) { - String emptyBluetoothDevice = "00:00:00:00:00:00"; + byte[] emptyBluetoothDeviceAddress = Utils.getBytesFromAddress("00:00:00:00:00:00"); if (mPendingOperation == REMOVE_BCAST_SOURCE) { - recvState = new BluetoothLeBroadcastReceiveState(mPendingSourceId, - BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType - btAdapter.getRemoteDevice(emptyBluetoothDevice), // sourceDevice - 0, // sourceAdvertisingSid - 0, // broadcastId - BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState - // bigEncryptionState - BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, - null, // badCode - 0, // numSubgroups - Arrays.asList(new Long[0]), // bisSyncState - Arrays.asList(new BluetoothLeAudioContentMetadata[0]) // subgroupMetadata - ); + recvState = + new BluetoothLeBroadcastReceiveState( + mPendingSourceId, + BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType + mAdapterService.getDeviceFromByte( + emptyBluetoothDeviceAddress), // sourceDev + 0, // sourceAdvertisingSid + 0, // broadcastId + BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState + // bigEncryptionState + BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, + null, // badCode + 0, // numSubgroups + Arrays.asList(new Long[0]), // bisSyncState + Arrays.asList( + new BluetoothLeAudioContentMetadata[0]) // subgroupMetadata + ); } else if (receiverState.length == 0) { if (mBluetoothLeBroadcastReceiveStates != null) { mNextSourceId = (byte) mBluetoothLeBroadcastReceiveStates.size(); @@ -772,19 +798,23 @@ public class BassClientStateMachine extends StateMachine { return null; } mNextSourceId++; - recvState = new BluetoothLeBroadcastReceiveState(mNextSourceId, - BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType - btAdapter.getRemoteDevice(emptyBluetoothDevice), // sourceDevice - 0, // sourceAdvertisingSid - 0, // broadcastId - BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState - // bigEncryptionState - BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, - null, // badCode - 0, // numSubgroups - Arrays.asList(new Long[0]), // bisSyncState - Arrays.asList(new BluetoothLeAudioContentMetadata[0]) // subgroupMetadata - ); + recvState = + new BluetoothLeBroadcastReceiveState( + mNextSourceId, + BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType + mAdapterService.getDeviceFromByte( + emptyBluetoothDeviceAddress), // sourceDev + 0, // sourceAdvertisingSid + 0, // broadcastId + BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState + // bigEncryptionState + BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, + null, // badCode + 0, // numSubgroups + Arrays.asList(new Long[0]), // bisSyncState + Arrays.asList( + new BluetoothLeAudioContentMetadata[0]) // subgroupMetadata + ); } } else { byte paSyncState = receiverState[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX]; @@ -845,9 +875,7 @@ public class BassClientStateMachine extends StateMachine { byte sourceAddressType = receiverState[BassConstants .BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX]; BassUtils.reverse(sourceAddress); - String address = Utils.getAddressStringFromByte(sourceAddress); - BluetoothDevice device = btAdapter.getRemoteLeDevice( - address, sourceAddressType); + BluetoothDevice device = mAdapterService.getDeviceFromByte(sourceAddress); byte sourceAdvSid = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ADV_SID_IDX]; recvState = new BluetoothLeBroadcastReceiveState( sourceId, diff --git a/android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java b/android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java index ee09cb9a3adee97122f821c534624672fc8b3f97..16f1d9918ba453fd3868b97ac2752a0a1af7f232 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassObjectsFactory.java @@ -22,6 +22,7 @@ import android.os.Looper; import android.util.Log; import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.FeatureFlags; import com.android.internal.annotations.VisibleForTesting; @@ -75,9 +76,10 @@ public class BassObjectsFactory { public BassClientStateMachine makeStateMachine( BluetoothDevice device, BassClientService svc, + AdapterService adapterService, Looper looper, FeatureFlags featureFlags) { - return BassClientStateMachine.make(device, svc, looper, featureFlags); + return BassClientStateMachine.make(device, svc, adapterService, looper, featureFlags); } /** diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java index 520e99101cab5f1c2ef87c0bae606c22df927c27..54a62dd1027031847103779da36273e789212f3d 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java @@ -239,7 +239,7 @@ public class BassClientServiceTest { return stateMachine; }) .when(mObjectsFactory) - .makeStateMachine(any(), any(), any(), any()); + .makeStateMachine(any(), any(), any(), any(), any()); doReturn(mBluetoothLeScannerWrapper).when(mObjectsFactory) .getBluetoothLeScannerWrapper(any()); @@ -350,7 +350,12 @@ public class BassClientServiceTest { assertThat(mBassClientService.connect(mCurrentDevice)).isTrue(); verify(mObjectsFactory) - .makeStateMachine(eq(mCurrentDevice), eq(mBassClientService), any(), any()); + .makeStateMachine( + eq(mCurrentDevice), + eq(mBassClientService), + eq(mAdapterService), + any(), + any()); BassClientStateMachine stateMachine = mStateMachines.get(mCurrentDevice); assertThat(stateMachine).isNotNull(); verify(stateMachine).sendMessage(BassClientStateMachine.CONNECT); diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java index 6b27bb927b8a88177e90b079baaff255872a5cb3..d9b63964bc22eb56c02b27bc7de868b8837c8eee 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java @@ -52,6 +52,7 @@ 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.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -70,21 +71,25 @@ import android.bluetooth.BluetoothLeBroadcastChannel; import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothLeBroadcastSubgroup; +import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.le.PeriodicAdvertisingCallback; import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import com.android.bluetooth.BluetoothMethodProxy; import com.android.bluetooth.TestUtils; +import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.flags.FakeFeatureFlagsImpl; import com.android.bluetooth.flags.FeatureFlags; @@ -101,7 +106,6 @@ 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; @@ -120,21 +124,32 @@ public class BassClientStateMachineTest { 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 static final String EMPTY_BLUETOOTH_DEVICE_ADDRESS = "00:00:00:00:00:00"; + private Context mTargetContext; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; private StubBassClientStateMachine mBassClientStateMachine; private BluetoothDevice mTestDevice; + private BluetoothDevice mSourceTestDevice; + private BluetoothDevice mEmptyTestDevice; private FakeFeatureFlagsImpl mFakeFlagsImpl; @Mock private AdapterService mAdapterService; @Mock private BassClientService mBassClientService; - @Spy private BluetoothMethodProxy mMethodProxy; + @Mock private BluetoothMethodProxy mMethodProxy; @Before public void setUp() throws Exception { + mTargetContext = InstrumentationRegistry.getTargetContext(); + BluetoothManager manager = mTargetContext.getSystemService(BluetoothManager.class); + assertThat(manager).isNotNull(); + mAdapter = manager.getAdapter(); + + mEmptyTestDevice = mAdapter.getRemoteDevice(EMPTY_BLUETOOTH_DEVICE_ADDRESS); + assertThat(mEmptyTestDevice).isNotNull(); + TestUtils.setAdapterService(mAdapterService); - mAdapter = BluetoothAdapter.getDefaultAdapter(); mFakeFlagsImpl = new FakeFeatureFlagsImpl(); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_MONITOR_SOURCE_SYNC_STATUS, false); mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false); @@ -143,7 +158,21 @@ public class BassClientStateMachineTest { any(), any(), anyInt(), anyInt()); // Get a device for testing - mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); + mTestDevice = TestUtils.getTestDevice(mAdapter, 0); + mSourceTestDevice = TestUtils.getTestDevice(mAdapter, 1); + + doReturn(mEmptyTestDevice) + .when(mAdapterService) + .getDeviceFromByte(Utils.getBytesFromAddress(EMPTY_BLUETOOTH_DEVICE_ADDRESS)); + doReturn(mTestDevice) + .when(mAdapterService) + .getDeviceFromByte(Utils.getBytesFromAddress(mTestDevice.getAddress())); + doReturn(mSourceTestDevice) + .when(mAdapterService) + .getDeviceFromByte(Utils.getBytesFromAddress(mSourceTestDevice.getAddress())); + doReturn(mEmptyTestDevice) + .when(mAdapterService) + .getDeviceFromByte(Utils.getBytesFromAddress(mEmptyTestDevice.getAddress())); // Set up thread and looper mHandlerThread = new HandlerThread("BassClientStateMachineTestHandlerThread"); @@ -152,14 +181,20 @@ public class BassClientStateMachineTest { new StubBassClientStateMachine( mTestDevice, mBassClientService, + mAdapterService, mHandlerThread.getLooper(), CONNECTION_TIMEOUT_MS, mFakeFlagsImpl); + assertThat(mBassClientStateMachine).isNotNull(); mBassClientStateMachine.start(); } @After public void tearDown() throws Exception { + if (mBassClientStateMachine == null) { + return; + } + mBassClientStateMachine.doQuit(); mHandlerThread.quit(); TestUtils.clearAdapterService(mAdapterService); @@ -707,23 +742,49 @@ public class BassClientStateMachineTest { 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 - }; + value = + new byte[] { + (byte) sourceId, // sourceId + (byte) (mSourceTestDevice.getAddressType() & 0xFF), // sourceAddressType + Utils.getByteAddress(mSourceTestDevice)[5], + Utils.getByteAddress(mSourceTestDevice)[4], + Utils.getByteAddress(mSourceTestDevice)[3], + Utils.getByteAddress(mSourceTestDevice)[2], + Utils.getByteAddress(mSourceTestDevice)[1], + Utils.getByteAddress(mSourceTestDevice)[0], // 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); @@ -1244,23 +1305,49 @@ public class BassClientStateMachineTest { 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 - }; + byte[] value = + new byte[] { + (byte) sourceId, // sourceId + (byte) (mSourceTestDevice.getAddressType() & 0xFF), // sourceAddressType + Utils.getByteAddress(mSourceTestDevice)[5], + Utils.getByteAddress(mSourceTestDevice)[4], + Utils.getByteAddress(mSourceTestDevice)[3], + Utils.getByteAddress(mSourceTestDevice)[2], + Utils.getByteAddress(mSourceTestDevice)[1], + Utils.getByteAddress(mSourceTestDevice)[0], // 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); @@ -1325,23 +1412,49 @@ public class BassClientStateMachineTest { // 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 - }; + byte[] value = + new byte[] { + (byte) sourceId, // sourceId + (byte) (mSourceTestDevice.getAddressType() & 0xFF), // sourceAddressType + Utils.getByteAddress(mSourceTestDevice)[5], + Utils.getByteAddress(mSourceTestDevice)[4], + Utils.getByteAddress(mSourceTestDevice)[3], + Utils.getByteAddress(mSourceTestDevice)[2], + Utils.getByteAddress(mSourceTestDevice)[1], + Utils.getByteAddress(mSourceTestDevice)[0], // 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 = @@ -2064,10 +2177,11 @@ public class BassClientStateMachineTest { StubBassClientStateMachine( BluetoothDevice device, BassClientService service, + AdapterService adapterService, Looper looper, int connectTimeout, FeatureFlags featureFlags) { - super(device, service, looper, connectTimeout, featureFlags); + super(device, service, adapterService, looper, connectTimeout, featureFlags); } @Override