| /* |
| * Copyright 2020 HIMSA II K/S - www.himsa.com. |
| * Represented by EHIMA - www.ehima.com |
| * |
| * 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.le_audio; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.anyString; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothLeAudio; |
| import android.bluetooth.BluetoothLeAudioCodecConfig; |
| import android.bluetooth.BluetoothLeAudioCodecStatus; |
| import android.bluetooth.BluetoothManager; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.IBluetoothLeAudioCallback; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioManager; |
| import android.media.BluetoothProfileConnectionInfo; |
| import android.os.Parcel; |
| 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.R; |
| import com.android.bluetooth.TestUtils; |
| import com.android.bluetooth.btservice.AdapterService; |
| import com.android.bluetooth.btservice.ServiceFactory; |
| import com.android.bluetooth.btservice.storage.DatabaseManager; |
| import com.android.bluetooth.hfp.HeadsetService; |
| import com.android.bluetooth.mcp.McpService; |
| import com.android.bluetooth.vc.VolumeControlService; |
| |
| import org.junit.After; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.Spy; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.TimeoutException; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class LeAudioServiceTest { |
| private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; |
| private static final int TIMEOUT_MS = 1000; |
| private static final int AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS = 3000; |
| private static final int MAX_LE_AUDIO_CONNECTIONS = 5; |
| private static final int LE_AUDIO_GROUP_ID_INVALID = -1; |
| |
| private BluetoothAdapter mAdapter; |
| private Context mTargetContext; |
| private LeAudioService mService; |
| private BluetoothDevice mLeftDevice; |
| private BluetoothDevice mRightDevice; |
| private BluetoothDevice mSingleDevice; |
| private HashSet<BluetoothDevice> mBondedDevices = new HashSet<>(); |
| private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap; |
| private LinkedBlockingQueue<Intent> mGroupIntentQueue = new LinkedBlockingQueue<>(); |
| private int testGroupId = 1; |
| private boolean onGroupStatusCallbackCalled = false; |
| private boolean onGroupCodecConfChangedCallbackCalled = false; |
| private BluetoothLeAudioCodecStatus testCodecStatus = null; |
| |
| private BroadcastReceiver mLeAudioIntentReceiver; |
| |
| @Mock private AdapterService mAdapterService; |
| @Mock private AudioManager mAudioManager; |
| @Mock private DatabaseManager mDatabaseManager; |
| @Mock private LeAudioNativeInterface mNativeInterface; |
| @Mock private LeAudioTmapGattServer mTmapGattServer; |
| @Mock private McpService mMcpService; |
| @Mock private VolumeControlService mVolumeControlService; |
| @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance(); |
| @Spy private ServiceFactory mServiceFactory = new ServiceFactory(); |
| |
| @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); |
| |
| private static final BluetoothLeAudioCodecConfig LC3_16KHZ_CONFIG = |
| new BluetoothLeAudioCodecConfig.Builder() |
| .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) |
| .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000) |
| .build(); |
| private static final BluetoothLeAudioCodecConfig LC3_48KHZ_CONFIG = |
| new BluetoothLeAudioCodecConfig.Builder() |
| .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) |
| .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000) |
| .build(); |
| |
| private static final BluetoothLeAudioCodecConfig LC3_48KHZ_16KHZ_CONFIG = |
| new BluetoothLeAudioCodecConfig.Builder() |
| .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) |
| .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000 |
| | BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000) |
| .build(); |
| |
| private static final List<BluetoothLeAudioCodecConfig> INPUT_CAPABILITIES_CONFIG = |
| new ArrayList() {{ |
| add(LC3_48KHZ_16KHZ_CONFIG); |
| }}; |
| |
| private static final List<BluetoothLeAudioCodecConfig> OUTPUT_CAPABILITIES_CONFIG = |
| new ArrayList() {{ |
| add(LC3_48KHZ_16KHZ_CONFIG); |
| }}; |
| |
| private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG = |
| new ArrayList() {{ |
| add(LC3_16KHZ_CONFIG); |
| }}; |
| |
| private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG = |
| new ArrayList() {{ |
| add(LC3_48KHZ_16KHZ_CONFIG); |
| }}; |
| |
| @Before |
| public void setUp() throws Exception { |
| mTargetContext = InstrumentationRegistry.getTargetContext(); |
| |
| // Set up mocks and test assets |
| MockitoAnnotations.initMocks(this); |
| |
| // Use spied objects factory |
| doNothing().when(mTmapGattServer).start(anyInt()); |
| doNothing().when(mTmapGattServer).stop(); |
| LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory); |
| doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any()); |
| |
| TestUtils.setAdapterService(mAdapterService); |
| doReturn(MAX_LE_AUDIO_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); |
| doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); |
| doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); |
| |
| BluetoothManager manager = mTargetContext.getSystemService(BluetoothManager.class); |
| assertThat(manager).isNotNull(); |
| mAdapter = manager.getAdapter(); |
| // Mock methods in AdapterService |
| doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( |
| mAdapterService).getBondedDevices(); |
| |
| LeAudioNativeInterface.setInstance(mNativeInterface); |
| startService(); |
| mService.mAudioManager = mAudioManager; |
| mService.mMcpService = mMcpService; |
| mService.mServiceFactory = mServiceFactory; |
| when(mServiceFactory.getVolumeControlService()).thenReturn(mVolumeControlService); |
| |
| LeAudioStackEvent stackEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED); |
| mService.messageFromNative(stackEvent); |
| assertThat(mService.mLeAudioNativeIsInitialized).isTrue(); |
| |
| // Override the timeout value to speed up the test |
| LeAudioStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s |
| |
| mGroupIntentQueue = new LinkedBlockingQueue<>(); |
| |
| // Set up the Connection State Changed receiver |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); |
| filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); |
| mLeAudioIntentReceiver = new LeAudioIntentReceiver(); |
| mTargetContext.registerReceiver(mLeAudioIntentReceiver, filter); |
| |
| doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( |
| mAdapterService).getBondedDevices(); |
| |
| // Get a device for testing |
| mLeftDevice = TestUtils.getTestDevice(mAdapter, 0); |
| mRightDevice = TestUtils.getTestDevice(mAdapter, 1); |
| mSingleDevice = TestUtils.getTestDevice(mAdapter, 2); |
| mDeviceQueueMap = new HashMap<>(); |
| mDeviceQueueMap.put(mLeftDevice, new LinkedBlockingQueue<>()); |
| mDeviceQueueMap.put(mRightDevice, new LinkedBlockingQueue<>()); |
| mDeviceQueueMap.put(mSingleDevice, new LinkedBlockingQueue<>()); |
| doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) |
| .getBondState(any(BluetoothDevice.class)); |
| doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| |
| verify(mNativeInterface, timeout(3000).times(1)).init(any()); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if ((mService == null) || (mAdapter == null)) { |
| return; |
| } |
| |
| mBondedDevices.clear(); |
| mGroupIntentQueue.clear(); |
| stopService(); |
| mTargetContext.unregisterReceiver(mLeAudioIntentReceiver); |
| mDeviceQueueMap.clear(); |
| TestUtils.clearAdapterService(mAdapterService); |
| LeAudioNativeInterface.setInstance(null); |
| } |
| |
| private void startService() throws TimeoutException { |
| TestUtils.startService(mServiceRule, LeAudioService.class); |
| mService = LeAudioService.getLeAudioService(); |
| assertThat(mService).isNotNull(); |
| } |
| |
| private void stopService() throws TimeoutException { |
| TestUtils.stopService(mServiceRule, LeAudioService.class); |
| mService = LeAudioService.getLeAudioService(); |
| assertThat(mService).isNull(); |
| } |
| |
| private class LeAudioIntentReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED |
| .equals(intent.getAction())) { |
| try { |
| BluetoothDevice device = intent.getParcelableExtra( |
| BluetoothDevice.EXTRA_DEVICE); |
| assertThat(device).isNotNull(); |
| LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device); |
| assertThat(queue).isNotNull(); |
| queue.put(intent); |
| } catch (InterruptedException e) { |
| assertWithMessage("Cannot add Intent to the Connection State queue: " |
| + e.getMessage()).fail(); |
| } |
| } |
| if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { |
| try { |
| BluetoothDevice device = intent.getParcelableExtra( |
| BluetoothDevice.EXTRA_DEVICE); |
| if (device != null) { |
| LinkedBlockingQueue<Intent> queue = mDeviceQueueMap.get(device); |
| assertThat(queue).isNotNull(); |
| queue.put(intent); |
| } |
| } catch (InterruptedException e) { |
| assertWithMessage("Cannot add Le Audio Intent to the Connection State queue: " |
| + e.getMessage()).fail(); |
| } |
| } |
| } |
| } |
| |
| private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, |
| int newState, int prevState) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device)); |
| assertThat(intent).isNotNull(); |
| assertThat(intent.getAction()) |
| .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED); |
| assertThat((BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).isEqualTo(device); |
| assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(newState); |
| assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)).isEqualTo(prevState); |
| } |
| |
| /** |
| * Test getting LeAudio Service: getLeAudioService() |
| */ |
| @Test |
| public void testGetLeAudioService() { |
| assertThat(mService).isEqualTo(LeAudioService.getLeAudioService()); |
| } |
| |
| /** |
| * Test stop LeAudio Service |
| */ |
| @Test |
| public void testStopLeAudioService() { |
| // Prepare: connect |
| connectDevice(mLeftDevice); |
| // LeAudio Service is already running: test stop(). Note: must be done on the main thread |
| InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { |
| public void run() { |
| assertThat(mService.stop()).isTrue(); |
| } |
| }); |
| } |
| |
| /** |
| * Test if stop during init is ok. |
| */ |
| @Test |
| public void testStopStartStopService() throws Exception { |
| InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { |
| public void run() { |
| assertThat(mService.stop()).isTrue(); |
| assertThat(mService.start()).isTrue(); |
| assertThat(mService.stop()).isTrue(); |
| assertThat(mService.start()).isTrue(); |
| } |
| }); |
| } |
| |
| /** |
| * Test get/set priority for BluetoothDevice |
| */ |
| @Test |
| public void testGetSetPriority() { |
| when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| assertWithMessage("Initial device priority") |
| .that(BluetoothProfile.CONNECTION_POLICY_UNKNOWN) |
| .isEqualTo(mService.getConnectionPolicy(mLeftDevice)); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| assertWithMessage("Setting device priority to PRIORITY_OFF") |
| .that(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) |
| .isEqualTo(mService.getConnectionPolicy(mLeftDevice)); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| assertWithMessage("Setting device priority to PRIORITY_ON") |
| .that(BluetoothProfile.CONNECTION_POLICY_ALLOWED) |
| .isEqualTo(mService.getConnectionPolicy(mLeftDevice)); |
| } |
| |
| /** |
| * Helper function to test okToConnect() method |
| * |
| * @param device test device |
| * @param bondState bond state value, could be invalid |
| * @param priority value, could be invalid, could be invalid |
| * @param expected expected result from okToConnect() |
| */ |
| private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority, |
| boolean expected) { |
| doReturn(bondState).when(mAdapterService).getBondState(device); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(priority); |
| assertThat(expected).isEqualTo(mService.okToConnect(device)); |
| } |
| |
| /** |
| * Test okToConnect method using various test cases |
| */ |
| @Test |
| public void testOkToConnect() { |
| int badPriorityValue = 1024; |
| int badBondState = 42; |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_NONE, badPriorityValue, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDING, badPriorityValue, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true); |
| testOkToConnectCase(mSingleDevice, |
| BluetoothDevice.BOND_BONDED, badPriorityValue, false); |
| testOkToConnectCase(mSingleDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mSingleDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mSingleDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mSingleDevice, |
| badBondState, badPriorityValue, false); |
| } |
| |
| /** |
| * Test that an outgoing connection to device that does not have Le Audio UUID is rejected |
| */ |
| @Test |
| public void testOutgoingConnectMissingLeAudioUuid() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Return No UUID |
| doReturn(new ParcelUuid[]{}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| assertWithMessage("Connect expected to fail").that(mService.connect(mLeftDevice)).isFalse(); |
| } |
| |
| /** |
| * Test that an outgoing connection to device with PRIORITY_OFF is rejected |
| */ |
| @Test |
| public void testOutgoingConnectPriorityOff() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Set the device priority to PRIORITY_OFF so connect() should fail |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| |
| // Send a connect request |
| assertWithMessage("Connect expected to fail").that(mService.connect(mLeftDevice)).isFalse(); |
| } |
| |
| /** |
| * Test that an outgoing connection times out |
| */ |
| @Test |
| public void testOutgoingConnectTimeout() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTING); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(LeAudioStateMachine.sConnectTimeoutMs * 2, |
| mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); |
| } |
| |
| private void injectNoVerifyDeviceConnected(BluetoothDevice device) { |
| generateUnexpectedConnectionMessageFromNative(device, |
| LeAudioStackEvent.CONNECTION_STATE_CONNECTED, |
| LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED); |
| } |
| |
| private void injectAndVerifyDeviceDisconnected(BluetoothDevice device) { |
| generateConnectionMessageFromNative(device, |
| LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, |
| LeAudioStackEvent.CONNECTION_STATE_CONNECTED); |
| } |
| |
| private void injectNoVerifyDeviceDisconnected(BluetoothDevice device) { |
| generateUnexpectedConnectionMessageFromNative(device, |
| LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED, |
| LeAudioStackEvent.CONNECTION_STATE_CONNECTED); |
| } |
| /** |
| * Test that the outgoing connect/disconnect and audio switch is successful. |
| */ |
| @Test |
| public void testAudioManagerConnectDisconnect() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); |
| assertWithMessage("Connect failed").that(mService.connect(mRightDevice)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTING); |
| verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getConnectionState(mRightDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTING); |
| |
| LeAudioStackEvent connCompletedEvent; |
| // Send a message to trigger connection completed |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mLeftDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTED); |
| |
| // Send a message to trigger connection completed for right side |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mRightDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state for right side |
| verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(mService.getConnectionState(mRightDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTED); |
| |
| // Verify the list of connected devices |
| assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isTrue(); |
| assertThat(mService.getConnectedDevices().contains(mRightDevice)).isTrue(); |
| |
| // Send a disconnect request |
| assertWithMessage("Disconnect failed").that(mService.disconnect(mLeftDevice)).isTrue(); |
| assertWithMessage("Disconnect failed").that(mService.disconnect(mRightDevice)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Disconnecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTING) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTING) |
| .isEqualTo(mService.getConnectionState(mRightDevice)); |
| |
| // Send a message to trigger disconnection completed |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mLeftDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| |
| // Send a message to trigger disconnection completed to the right device |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mRightDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mRightDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mRightDevice)); |
| |
| // Verify the list of connected devices |
| assertThat(mService.getConnectedDevices().contains(mLeftDevice)).isFalse(); |
| assertThat(mService.getConnectedDevices().contains(mRightDevice)).isFalse(); |
| } |
| |
| /** |
| * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING Le Audio stack |
| * events will create a state machine. |
| */ |
| @Test |
| public void testCreateStateMachineStackEvents() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Create device descriptor with connect request |
| assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); |
| |
| // Le Audio stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| |
| // stack event: CONNECTION_STATE_CONNECTED - state machine should be created |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| |
| // stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created |
| generateUnexpectedConnectionMessageFromNative(mLeftDevice, |
| BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| |
| // stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created |
| generateUnexpectedConnectionMessageFromNative(mLeftDevice, |
| BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| } |
| |
| /** |
| * Test that a state machine in DISCONNECTED state is removed only after the device is unbond. |
| */ |
| @Test |
| public void testDeleteStateMachineUnbondEvents() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Create device descriptor with connect request |
| assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTING); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| // Device unbond - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| |
| // LeAudio stack event: CONNECTION_STATE_CONNECTED - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED); |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTED); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| // Device unbond - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_DISCONNECTING); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_DISCONNECTING); |
| // Device unbond - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED); |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| assertThat(mService.getConnectionState(mLeftDevice)) |
| .isEqualTo(BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| // Device unbond - state machine is removed |
| mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| } |
| |
| /** |
| * Test that a CONNECTION_STATE_DISCONNECTED Le Audio stack event will remove the state |
| * machine only if the device is unbond. |
| */ |
| @Test |
| public void testDeleteStateMachineDisconnectEvents() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mLeftDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mRightDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| // Create device descriptor with connect request |
| assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine remains |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // Device bond state marked as unbond - state machine is not removed |
| doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService) |
| .getBondState(any(BluetoothDevice.class)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isTrue(); |
| |
| // LeAudio stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed |
| generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mLeftDevice)); |
| assertThat(mService.getDevices().contains(mLeftDevice)).isFalse(); |
| } |
| |
| private void connectDevice(BluetoothDevice device) { |
| LeAudioStackEvent connCompletedEvent; |
| |
| List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices(); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mNativeInterface).connectLeAudio(device); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(device); |
| |
| // Send a connect request |
| assertWithMessage("Connect failed").that(mService.connect(device)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(device)); |
| |
| // Send a message to trigger connection completed |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = device; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(BluetoothProfile.STATE_CONNECTED) |
| .isEqualTo(mService.getConnectionState(device)); |
| |
| // Verify that the device is in the list of connected devices |
| assertThat(mService.getConnectedDevices().contains(device)).isTrue(); |
| // Verify the list of previously connected devices |
| for (BluetoothDevice prevDevice : prevConnectedDevices) { |
| assertThat(mService.getConnectedDevices().contains(prevDevice)).isTrue(); |
| } |
| } |
| |
| private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, |
| int oldConnectionState) { |
| LeAudioStackEvent stackEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt1 = newConnectionState; |
| mService.messageFromNative(stackEvent); |
| // Verify the connection state broadcast |
| verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState); |
| } |
| |
| private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, |
| int newConnectionState, int oldConnectionState) { |
| LeAudioStackEvent stackEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt1 = newConnectionState; |
| mService.messageFromNative(stackEvent); |
| // Verify the connection state broadcast |
| verifyNoConnectionStateIntent(TIMEOUT_MS, device); |
| } |
| |
| private void generateGroupNodeAdded(BluetoothDevice device, int groupId) { |
| LeAudioStackEvent nodeGroupAdded = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); |
| nodeGroupAdded.device = device; |
| nodeGroupAdded.valueInt1 = groupId; |
| nodeGroupAdded.valueInt2 = LeAudioStackEvent.GROUP_NODE_ADDED; |
| mService.messageFromNative(nodeGroupAdded); |
| } |
| |
| private void generateGroupNodeRemoved(BluetoothDevice device, int groupId) { |
| LeAudioStackEvent nodeGroupRemoved = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); |
| nodeGroupRemoved.device = device; |
| nodeGroupRemoved.valueInt1 = groupId; |
| nodeGroupRemoved.valueInt2 = LeAudioStackEvent.GROUP_NODE_REMOVED; |
| mService.messageFromNative(nodeGroupRemoved); |
| } |
| |
| private void verifyNoConnectionStateIntent(int timeoutMs, BluetoothDevice device) { |
| Intent intent = TestUtils.waitForNoIntent(timeoutMs, mDeviceQueueMap.get(device)); |
| assertThat(intent).isNull(); |
| } |
| |
| /** |
| * Test setting connection policy |
| */ |
| @Test |
| public void testSetConnectionPolicy() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mDatabaseManager).setProfileConnectionPolicy(any(BluetoothDevice.class), |
| anyInt(), anyInt()); |
| when(mDatabaseManager.getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| |
| assertThat(mService.setConnectionPolicy(mSingleDevice, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(mSingleDevice)); |
| |
| LeAudioStackEvent connCompletedEvent; |
| // Send a message to trigger connection completed |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mSingleDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| assertThat(BluetoothProfile.STATE_CONNECTED) |
| .isEqualTo(mService.getConnectionState(mSingleDevice)); |
| |
| // Set connection policy to forbidden |
| assertThat(mService.setConnectionPolicy(mSingleDevice, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue(); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| assertThat(BluetoothProfile.STATE_DISCONNECTING) |
| .isEqualTo(mService.getConnectionState(mSingleDevice)); |
| |
| // Send a message to trigger disconnection completed |
| connCompletedEvent = new LeAudioStackEvent( |
| LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mSingleDevice; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mSingleDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| assertThat(BluetoothProfile.STATE_DISCONNECTED) |
| .isEqualTo(mService.getConnectionState(mSingleDevice)); |
| } |
| |
| /** |
| * Helper function to connect Test device |
| * |
| * @param device test device |
| */ |
| private void connectTestDevice(BluetoothDevice device, int GroupId) { |
| List<BluetoothDevice> prevConnectedDevices = mService.getConnectedDevices(); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| // Send a connect request |
| assertWithMessage("Connect failed").that(mService.connect(device)).isTrue(); |
| |
| // Make device bonded |
| mBondedDevices.add(device); |
| |
| // Wait ASYNC_CALL_TIMEOUT_MILLIS for state to settle, timing is also tested here and |
| // 250ms for processing two messages should be way more than enough. Anything that breaks |
| // this indicate some breakage in other part of Android OS |
| |
| verifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); |
| assertThat(BluetoothProfile.STATE_CONNECTING) |
| .isEqualTo(mService.getConnectionState(device)); |
| |
| // Use connected event to indicate that device is connected |
| LeAudioStackEvent connCompletedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = device; |
| connCompletedEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_CONNECTED; |
| mService.messageFromNative(connCompletedEvent); |
| |
| verifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); |
| |
| assertThat(BluetoothProfile.STATE_CONNECTED) |
| .isEqualTo(mService.getConnectionState(device)); |
| |
| LeAudioStackEvent nodeGroupAdded = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); |
| nodeGroupAdded.device = device; |
| nodeGroupAdded.valueInt1 = GroupId; |
| nodeGroupAdded.valueInt2 = LeAudioStackEvent.GROUP_NODE_ADDED; |
| mService.messageFromNative(nodeGroupAdded); |
| |
| // Verify that the device is in the list of connected devices |
| assertThat(mService.getConnectedDevices().contains(device)).isTrue(); |
| // Verify the list of previously connected devices |
| for (BluetoothDevice prevDevice : prevConnectedDevices) { |
| assertThat(mService.getConnectedDevices().contains(prevDevice)).isTrue(); |
| } |
| } |
| |
| /** |
| * Test adding node |
| */ |
| @Test |
| public void testGroupAddRemoveNode() { |
| int groupId = 1; |
| |
| doReturn(true).when(mNativeInterface).groupAddNode(groupId, mSingleDevice); |
| doReturn(true).when(mNativeInterface).groupRemoveNode(groupId, mSingleDevice); |
| |
| assertThat(mService.groupAddNode(groupId, mSingleDevice)).isTrue(); |
| assertThat(mService.groupRemoveNode(groupId, mSingleDevice)).isTrue(); |
| } |
| |
| /** |
| * Test setting active device group |
| */ |
| @Test |
| public void testSetActiveDeviceGroup() { |
| int groupId = 1; |
| |
| // Not connected device |
| assertThat(mService.setActiveDevice(mSingleDevice)).isFalse(); |
| |
| // Connected device |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); |
| |
| // no active device |
| assertThat(mService.setActiveDevice(null)).isTrue(); |
| } |
| |
| /** |
| * Test getting active device |
| */ |
| @Test |
| public void testGetActiveDevices() { |
| int groupId = 1; |
| /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ |
| int direction = 1; |
| int snkAudioLocation = 3; |
| int srcAudioLocation = 4; |
| int availableContexts = 5; |
| int nodeStatus = LeAudioStackEvent.GROUP_NODE_ADDED; |
| int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; |
| |
| // Single active device |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| // Add device to group |
| LeAudioStackEvent nodeStatusChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); |
| nodeStatusChangedEvent.device = mSingleDevice; |
| nodeStatusChangedEvent.valueInt1 = groupId; |
| nodeStatusChangedEvent.valueInt2 = nodeStatus; |
| mService.messageFromNative(nodeStatusChangedEvent); |
| |
| assertThat(mService.setActiveDevice(mSingleDevice)).isTrue(); |
| |
| // Add location support |
| LeAudioStackEvent audioConfChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); |
| audioConfChangedEvent.device = mSingleDevice; |
| audioConfChangedEvent.valueInt1 = direction; |
| audioConfChangedEvent.valueInt2 = groupId; |
| audioConfChangedEvent.valueInt3 = snkAudioLocation; |
| audioConfChangedEvent.valueInt4 = srcAudioLocation; |
| audioConfChangedEvent.valueInt5 = availableContexts; |
| mService.messageFromNative(audioConfChangedEvent); |
| |
| // Set group and device as active |
| LeAudioStackEvent groupStatusChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); |
| groupStatusChangedEvent.device = mSingleDevice; |
| groupStatusChangedEvent.valueInt1 = groupId; |
| groupStatusChangedEvent.valueInt2 = groupStatus; |
| mService.messageFromNative(groupStatusChangedEvent); |
| |
| assertThat(mService.getActiveDevices().contains(mSingleDevice)).isTrue(); |
| |
| // Remove device from group |
| groupStatusChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_NODE_STATUS_CHANGED); |
| groupStatusChangedEvent.device = mSingleDevice; |
| groupStatusChangedEvent.valueInt1 = groupId; |
| groupStatusChangedEvent.valueInt2 = LeAudioStackEvent.GROUP_NODE_REMOVED; |
| mService.messageFromNative(groupStatusChangedEvent); |
| |
| assertThat(mService.getActiveDevices().contains(mSingleDevice)).isFalse(); |
| } |
| |
| private void injectGroupStatusChange(int groupId, int groupStatus) { |
| int eventType = LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED; |
| LeAudioStackEvent groupStatusChangedEvent = new LeAudioStackEvent(eventType); |
| groupStatusChangedEvent.valueInt1 = groupId; |
| groupStatusChangedEvent.valueInt2 = groupStatus; |
| mService.messageFromNative(groupStatusChangedEvent); |
| } |
| |
| private void injectAudioConfChanged(int groupId, Integer availableContexts, int direction) { |
| int snkAudioLocation = 3; |
| int srcAudioLocation = 4; |
| int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED; |
| |
| // Add device to group |
| LeAudioStackEvent audioConfChangedEvent = new LeAudioStackEvent(eventType); |
| audioConfChangedEvent.device = mSingleDevice; |
| audioConfChangedEvent.valueInt1 = direction; |
| audioConfChangedEvent.valueInt2 = groupId; |
| audioConfChangedEvent.valueInt3 = snkAudioLocation; |
| audioConfChangedEvent.valueInt4 = srcAudioLocation; |
| audioConfChangedEvent.valueInt5 = availableContexts; |
| mService.messageFromNative(audioConfChangedEvent); |
| } |
| /** |
| * Test native interface audio configuration changed message handling |
| */ |
| @Test |
| @Ignore("b/258573934") |
| public void testMessageFromNativeAudioConfChangedActiveGroup() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| injectAudioConfChanged(testGroupId, BluetoothLeAudio.CONTEXT_TYPE_MEDIA | |
| BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL, 3); |
| injectGroupStatusChange(testGroupId, BluetoothLeAudio.GROUP_STATUS_ACTIVE); |
| |
| /* Expect 2 calles to Audio Manager - one for output and second for input as this is |
| * Conversational use case */ |
| verify(mAudioManager, times(2)).handleBluetoothActiveDeviceChanged(any(), any(), |
| any(BluetoothProfileConnectionInfo.class)); |
| /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback |
| * mAudioManager.onAudioDeviceAdded |
| */ |
| mService.notifyActiveDeviceChanged(); |
| |
| String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED; |
| Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice)); |
| assertThat(intent).isNotNull(); |
| assertThat(action).isEqualTo(intent.getAction()); |
| assertThat(mSingleDevice) |
| .isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); |
| } |
| /** |
| * Test native interface audio configuration changed message handling |
| */ |
| @Test |
| public void testMessageFromNativeAudioConfChangedInactiveGroup() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED; |
| Integer contexts = BluetoothLeAudio.CONTEXT_TYPE_MEDIA | |
| BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL; |
| injectAudioConfChanged(testGroupId, contexts, 3); |
| |
| Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice)); |
| assertThat(intent).isNull(); |
| } |
| /** |
| * Test native interface audio configuration changed message handling |
| */ |
| @Test |
| public void testMessageFromNativeAudioConfChangedNoGroupChanged() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED; |
| |
| injectAudioConfChanged(testGroupId, 0, 3); |
| Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice)); |
| assertThat(intent).isNull(); |
| } |
| |
| |
| private void sendEventAndVerifyIntentForGroupStatusChanged(int groupId, int groupStatus) { |
| |
| onGroupStatusCallbackCalled = false; |
| |
| IBluetoothLeAudioCallback leAudioCallbacks = |
| new IBluetoothLeAudioCallback.Stub() { |
| @Override |
| public void onCodecConfigChanged(int gid, BluetoothLeAudioCodecStatus status) {} |
| @Override |
| public void onGroupStatusChanged(int gid, int gStatus) { |
| onGroupStatusCallbackCalled = true; |
| assertThat(gid == groupId).isTrue(); |
| assertThat(gStatus == groupStatus).isTrue(); |
| } |
| @Override |
| public void onGroupNodeAdded(BluetoothDevice device, int gid) {} |
| @Override |
| public void onGroupNodeRemoved(BluetoothDevice device, int gid) {} |
| }; |
| |
| mService.mLeAudioCallbacks.register(leAudioCallbacks); |
| |
| injectGroupStatusChange(groupId, groupStatus); |
| |
| TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); |
| assertThat(onGroupStatusCallbackCalled).isTrue(); |
| |
| onGroupStatusCallbackCalled = false; |
| mService.mLeAudioCallbacks.unregister(leAudioCallbacks); |
| } |
| |
| /** |
| * Test native interface group status message handling |
| */ |
| @Test |
| public void testMessageFromNativeGroupStatusChanged() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| injectAudioConfChanged(testGroupId, BluetoothLeAudio.CONTEXT_TYPE_MEDIA | |
| BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL, 3); |
| |
| sendEventAndVerifyIntentForGroupStatusChanged(testGroupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); |
| sendEventAndVerifyIntentForGroupStatusChanged(testGroupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); |
| } |
| |
| private void injectLocalCodecConfigCapaChanged(List<BluetoothLeAudioCodecConfig> inputCodecCapa, |
| List<BluetoothLeAudioCodecConfig> outputCodecCapa) { |
| int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_LOCAL_CODEC_CONFIG_CAPA_CHANGED; |
| |
| // Add device to group |
| LeAudioStackEvent localCodecCapaEvent = new LeAudioStackEvent(eventType); |
| localCodecCapaEvent.valueCodecList1 = inputCodecCapa; |
| localCodecCapaEvent.valueCodecList2 = outputCodecCapa; |
| mService.messageFromNative(localCodecCapaEvent); |
| } |
| |
| private void injectGroupCodecConfigChanged(int groupId, BluetoothLeAudioCodecConfig inputCodecConfig, |
| BluetoothLeAudioCodecConfig outputCodecConfig, |
| List<BluetoothLeAudioCodecConfig> inputSelectableCodecConfig, |
| List<BluetoothLeAudioCodecConfig> outputSelectableCodecConfig) { |
| int eventType = LeAudioStackEvent.EVENT_TYPE_AUDIO_GROUP_CODEC_CONFIG_CHANGED; |
| |
| // Add device to group |
| LeAudioStackEvent groupCodecConfigChangedEvent = new LeAudioStackEvent(eventType); |
| groupCodecConfigChangedEvent.valueInt1 = groupId; |
| groupCodecConfigChangedEvent.valueCodec1 = inputCodecConfig; |
| groupCodecConfigChangedEvent.valueCodec2 = outputCodecConfig; |
| groupCodecConfigChangedEvent.valueCodecList1 = inputSelectableCodecConfig; |
| groupCodecConfigChangedEvent.valueCodecList2 = outputSelectableCodecConfig; |
| mService.messageFromNative(groupCodecConfigChangedEvent); |
| } |
| |
| /** |
| * Test native interface group status message handling |
| */ |
| @Test |
| public void testMessageFromNativeGroupCodecConfigChanged() { |
| onGroupCodecConfChangedCallbackCalled = false; |
| |
| injectLocalCodecConfigCapaChanged(INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG); |
| |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| testCodecStatus = new BluetoothLeAudioCodecStatus(LC3_16KHZ_CONFIG, |
| LC3_48KHZ_CONFIG, INPUT_CAPABILITIES_CONFIG, |
| OUTPUT_CAPABILITIES_CONFIG, INPUT_SELECTABLE_CONFIG, |
| OUTPUT_SELECTABLE_CONFIG); |
| |
| IBluetoothLeAudioCallback leAudioCallbacks = |
| new IBluetoothLeAudioCallback.Stub() { |
| @Override |
| public void onCodecConfigChanged(int gid, BluetoothLeAudioCodecStatus status) { |
| onGroupCodecConfChangedCallbackCalled = true; |
| assertThat(status.equals(testCodecStatus)).isTrue(); |
| } |
| @Override |
| public void onGroupStatusChanged(int gid, int gStatus) {} |
| @Override |
| public void onGroupNodeAdded(BluetoothDevice device, int gid) {} |
| @Override |
| public void onGroupNodeRemoved(BluetoothDevice device, int gid) {} |
| }; |
| |
| mService.mLeAudioCallbacks.register(leAudioCallbacks); |
| |
| injectGroupCodecConfigChanged(testGroupId, LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG, |
| INPUT_SELECTABLE_CONFIG, |
| OUTPUT_SELECTABLE_CONFIG); |
| |
| TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper()); |
| assertThat(onGroupCodecConfChangedCallbackCalled).isTrue(); |
| |
| onGroupCodecConfChangedCallbackCalled = false; |
| mService.mLeAudioCallbacks.unregister(leAudioCallbacks); |
| } |
| |
| private void verifyActiveDeviceStateIntent(int timeoutMs, BluetoothDevice device) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mDeviceQueueMap.get(device)); |
| assertThat(intent).isNotNull(); |
| assertThat(intent.getAction()) |
| .isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED); |
| assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); |
| } |
| |
| /** |
| * Test native interface group status message handling |
| */ |
| @Test |
| @Ignore("b/258573934") |
| public void testLeadGroupDeviceDisconnects() { |
| int groupId = 1; |
| /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ |
| int direction = 1; |
| int snkAudioLocation = 3; |
| int srcAudioLocation = 4; |
| int availableContexts = 5; |
| int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; |
| BluetoothDevice leadDevice; |
| BluetoothDevice memberDevice = mLeftDevice; |
| |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, groupId); |
| connectTestDevice(mRightDevice, groupId); |
| |
| leadDevice = mService.getConnectedGroupLeadDevice(groupId); |
| if (Objects.equals(leadDevice, mLeftDevice)) { |
| memberDevice = mRightDevice; |
| } |
| |
| assertThat(mService.setActiveDevice(leadDevice)).isTrue(); |
| |
| //Add location support |
| LeAudioStackEvent audioConfChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); |
| audioConfChangedEvent.valueInt1 = direction; |
| audioConfChangedEvent.valueInt2 = groupId; |
| audioConfChangedEvent.valueInt3 = snkAudioLocation; |
| audioConfChangedEvent.valueInt4 = srcAudioLocation; |
| audioConfChangedEvent.valueInt5 = availableContexts; |
| mService.messageFromNative(audioConfChangedEvent); |
| |
| //Set group and device as active |
| LeAudioStackEvent groupStatusChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); |
| groupStatusChangedEvent.valueInt1 = groupId; |
| groupStatusChangedEvent.valueInt2 = groupStatus; |
| mService.messageFromNative(groupStatusChangedEvent); |
| |
| assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), |
| any(BluetoothProfileConnectionInfo.class)); |
| /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback |
| * mAudioManager.onAudioDeviceAdded |
| */ |
| mService.notifyActiveDeviceChanged(); |
| doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService).getBondState(leadDevice); |
| verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, leadDevice); |
| injectNoVerifyDeviceDisconnected(leadDevice); |
| |
| // We should not change the audio device |
| assertThat(mService.getConnectionState(leadDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTED); |
| |
| injectAndVerifyDeviceDisconnected(memberDevice); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, leadDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(leadDevice), |
| any(BluetoothProfileConnectionInfo.class)); |
| |
| } |
| |
| /** |
| * Test native interface group status message handling |
| */ |
| @Test |
| @Ignore("b/258573934") |
| public void testLeadGroupDeviceReconnects() { |
| int groupId = 1; |
| /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ |
| int direction = 1; |
| int snkAudioLocation = 3; |
| int srcAudioLocation = 4; |
| int availableContexts = 5; |
| int groupStatus = LeAudioStackEvent.GROUP_STATUS_ACTIVE; |
| BluetoothDevice leadDevice; |
| BluetoothDevice memberDevice = mLeftDevice; |
| |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, groupId); |
| connectTestDevice(mRightDevice, groupId); |
| |
| leadDevice = mService.getConnectedGroupLeadDevice(groupId); |
| if (Objects.equals(leadDevice, mLeftDevice)) { |
| memberDevice = mRightDevice; |
| } |
| |
| assertThat(mService.setActiveDevice(leadDevice)).isTrue(); |
| |
| //Add location support |
| LeAudioStackEvent audioConfChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); |
| audioConfChangedEvent.valueInt1 = direction; |
| audioConfChangedEvent.valueInt2 = groupId; |
| audioConfChangedEvent.valueInt3 = snkAudioLocation; |
| audioConfChangedEvent.valueInt4 = srcAudioLocation; |
| audioConfChangedEvent.valueInt5 = availableContexts; |
| mService.messageFromNative(audioConfChangedEvent); |
| |
| //Set group and device as active |
| LeAudioStackEvent groupStatusChangedEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); |
| groupStatusChangedEvent.valueInt1 = groupId; |
| groupStatusChangedEvent.valueInt2 = groupStatus; |
| mService.messageFromNative(groupStatusChangedEvent); |
| |
| assertThat(mService.getActiveDevices().contains(leadDevice)).isTrue(); |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(leadDevice), any(), |
| any(BluetoothProfileConnectionInfo.class)); |
| /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback |
| * mAudioManager.onAudioDeviceAdded |
| */ |
| mService.notifyActiveDeviceChanged(); |
| |
| verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, leadDevice); |
| /* We don't want to distribute DISCONNECTION event, instead will try to reconnect |
| * (in native) |
| */ |
| injectNoVerifyDeviceDisconnected(leadDevice); |
| assertThat(mService.getConnectionState(leadDevice)) |
| .isEqualTo(BluetoothProfile.STATE_CONNECTED); |
| |
| /* Reconnect device, there should be no intent about that, as device was pretending |
| * connected |
| */ |
| injectNoVerifyDeviceConnected(leadDevice); |
| |
| injectAndVerifyDeviceDisconnected(memberDevice); |
| injectAndVerifyDeviceDisconnected(leadDevice); |
| |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice), |
| any(BluetoothProfileConnectionInfo.class)); |
| } |
| |
| /** |
| * Test volume caching for the group |
| */ |
| @Test |
| public void testVolumeCache() { |
| int groupId = 1; |
| int volume = 100; |
| /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */ |
| int direction = 1; |
| int availableContexts = 4; |
| |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, groupId); |
| connectTestDevice(mRightDevice, groupId); |
| |
| assertThat(mService.setActiveDevice(mLeftDevice)).isTrue(); |
| |
| ArgumentCaptor<BluetoothProfileConnectionInfo> profileInfo = |
| ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); |
| |
| //Add location support. |
| injectAudioConfChanged(groupId, availableContexts, direction); |
| |
| doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId); |
| //Set group and device as active. |
| injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); |
| |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(null), |
| profileInfo.capture()); |
| assertThat(profileInfo.getValue().getVolume()).isEqualTo(-1); |
| |
| mService.setVolume(volume); |
| verify(mVolumeControlService, times(1)).setGroupVolume(groupId, volume); |
| |
| // Set group to inactive. |
| injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE); |
| |
| verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), any(), |
| any(BluetoothProfileConnectionInfo.class)); |
| |
| doReturn(100).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId); |
| |
| //Set back to active and check if last volume is restored. |
| injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE); |
| |
| verify(mAudioManager, times(2)).handleBluetoothActiveDeviceChanged(any(), eq(null), |
| profileInfo.capture()); |
| |
| assertThat(profileInfo.getValue().getVolume()).isEqualTo(volume); |
| } |
| |
| @Test |
| public void testGetAudioDeviceGroupVolume_whenVolumeControlServiceIsNull() { |
| mService.mVolumeControlService = null; |
| doReturn(null).when(mServiceFactory).getVolumeControlService(); |
| |
| int groupId = 1; |
| assertThat(mService.getAudioDeviceGroupVolume(groupId)).isEqualTo(-1); |
| } |
| |
| @Test |
| public void testGetAudioLocation() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| assertThat(mService.getAudioLocation(null)) |
| .isEqualTo(BluetoothLeAudio.AUDIO_LOCATION_INVALID); |
| |
| int sinkAudioLocation = 10; |
| LeAudioStackEvent stackEvent = |
| new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE); |
| stackEvent.device = mSingleDevice; |
| stackEvent.valueInt1 = sinkAudioLocation; |
| mService.messageFromNative(stackEvent); |
| |
| assertThat(mService.getAudioLocation(mSingleDevice)).isEqualTo(sinkAudioLocation); |
| } |
| |
| @Test |
| public void testGetConnectedPeerDevices() { |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, testGroupId); |
| connectTestDevice(mRightDevice, testGroupId); |
| |
| List<BluetoothDevice> peerDevices = mService.getConnectedPeerDevices(testGroupId); |
| assertThat(peerDevices.contains(mLeftDevice)).isTrue(); |
| assertThat(peerDevices.contains(mRightDevice)).isTrue(); |
| } |
| |
| @Test |
| public void testGetDevicesMatchingConnectionStates() { |
| assertThat(mService.getDevicesMatchingConnectionStates(null)).isEmpty(); |
| |
| int[] states = new int[] { BluetoothProfile.STATE_CONNECTED }; |
| doReturn(null).when(mAdapterService).getBondedDevices(); |
| assertThat(mService.getDevicesMatchingConnectionStates(states)).isEmpty(); |
| |
| doReturn(new BluetoothDevice[] { mSingleDevice }).when(mAdapterService).getBondedDevices(); |
| assertThat(mService.getDevicesMatchingConnectionStates(states)).isEmpty(); |
| } |
| |
| @Test |
| public void testDefaultValuesOfSeveralGetters() { |
| assertThat(mService.getMaximumNumberOfBroadcasts()).isEqualTo(1); |
| assertThat(mService.isPlaying(100)).isFalse(); |
| assertThat(mService.isValidDeviceGroup(LE_AUDIO_GROUP_ID_INVALID)).isFalse(); |
| } |
| |
| @Test |
| public void testHandleGroupIdleDuringCall() { |
| BluetoothDevice headsetDevice = TestUtils.getTestDevice(mAdapter, 5); |
| HeadsetService headsetService = Mockito.mock(HeadsetService.class); |
| when(mServiceFactory.getHeadsetService()).thenReturn(headsetService); |
| |
| mService.mHfpHandoverDevice = null; |
| mService.handleGroupIdleDuringCall(); |
| verify(headsetService, never()).getActiveDevice(); |
| |
| mService.mHfpHandoverDevice = headsetDevice; |
| when(headsetService.getActiveDevice()).thenReturn(headsetDevice); |
| mService.handleGroupIdleDuringCall(); |
| verify(headsetService).connectAudio(); |
| assertThat(mService.mHfpHandoverDevice).isNull(); |
| |
| mService.mHfpHandoverDevice = headsetDevice; |
| when(headsetService.getActiveDevice()).thenReturn(null); |
| mService.handleGroupIdleDuringCall(); |
| verify(headsetService).setActiveDevice(headsetDevice); |
| assertThat(mService.mHfpHandoverDevice).isNull(); |
| } |
| |
| @Test |
| public void testDump_doesNotCrash() { |
| doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| doReturn(new BluetoothDevice[]{mSingleDevice}).when(mAdapterService).getBondedDevices(); |
| when(mDatabaseManager |
| .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.LE_AUDIO)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class)); |
| |
| connectTestDevice(mSingleDevice, testGroupId); |
| |
| StringBuilder sb = new StringBuilder(); |
| mService.dump(sb); |
| } |
| |
| /** |
| * Test setting authorization for LeAudio device in the McpService |
| */ |
| @Test |
| public void testAuthorizeMcpServiceWhenDeviceConnecting() { |
| int groupId = 1; |
| |
| mService.handleBluetoothEnabled(); |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, groupId); |
| connectTestDevice(mRightDevice, groupId); |
| verify(mMcpService, times(1)).setDeviceAuthorized(mLeftDevice, true); |
| verify(mMcpService, times(1)).setDeviceAuthorized(mRightDevice, true); |
| } |
| |
| /** |
| * Test setting authorization for LeAudio device in the McpService |
| */ |
| @Test |
| public void testAuthorizeMcpServiceOnBluetoothEnableAndNodeRemoval() { |
| int groupId = 1; |
| |
| doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class)); |
| connectTestDevice(mLeftDevice, groupId); |
| connectTestDevice(mRightDevice, groupId); |
| |
| generateGroupNodeAdded(mLeftDevice, groupId); |
| generateGroupNodeAdded(mRightDevice, groupId); |
| |
| verify(mMcpService, times(0)).setDeviceAuthorized(mLeftDevice, true); |
| verify(mMcpService, times(0)).setDeviceAuthorized(mRightDevice, true); |
| |
| mService.handleBluetoothEnabled(); |
| |
| verify(mMcpService, times(1)).setDeviceAuthorized(mLeftDevice, true); |
| verify(mMcpService, times(1)).setDeviceAuthorized(mRightDevice, true); |
| |
| generateGroupNodeRemoved(mLeftDevice, groupId); |
| verify(mMcpService, times(1)).setDeviceAuthorized(mLeftDevice, false); |
| |
| generateGroupNodeRemoved(mRightDevice, groupId); |
| verify(mMcpService, times(1)).setDeviceAuthorized(mRightDevice, false); |
| } |
| } |