| /* |
| * Copyright 2018 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.hfp; |
| |
| import static org.mockito.Mockito.*; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothHeadset; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.bluetooth.IBluetoothHeadset; |
| import android.content.Context; |
| import android.media.AudioManager; |
| import android.os.ParcelUuid; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| |
| 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.storage.DatabaseManager; |
| |
| import org.hamcrest.Matchers; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Assume; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.Spy; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Set; |
| |
| /** |
| * Tests for {@link HeadsetService} |
| */ |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class HeadsetServiceTest { |
| private static final int MAX_HEADSET_CONNECTIONS = 5; |
| private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP}; |
| private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; |
| private static final String TEST_PHONE_NUMBER = "1234567890"; |
| |
| private Context mTargetContext; |
| private HeadsetService mHeadsetService; |
| private IBluetoothHeadset.Stub mHeadsetServiceBinder; |
| private BluetoothAdapter mAdapter; |
| private HeadsetNativeInterface mNativeInterface; |
| private BluetoothDevice mCurrentDevice; |
| private final HashMap<BluetoothDevice, HeadsetStateMachine> mStateMachines = new HashMap<>(); |
| |
| @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); |
| |
| @Spy private HeadsetObjectsFactory mObjectsFactory = HeadsetObjectsFactory.getInstance(); |
| @Mock private AdapterService mAdapterService; |
| @Mock private DatabaseManager mDatabaseManager; |
| @Mock private HeadsetSystemInterface mSystemInterface; |
| @Mock private AudioManager mAudioManager; |
| @Mock private HeadsetPhoneState mPhoneState; |
| |
| @Before |
| public void setUp() throws Exception { |
| mTargetContext = InstrumentationRegistry.getTargetContext(); |
| Assume.assumeTrue("Ignore test when HeadsetService is not enabled", |
| mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)); |
| MockitoAnnotations.initMocks(this); |
| TestUtils.setAdapterService(mAdapterService); |
| // We cannot mock HeadsetObjectsFactory.getInstance() with Mockito. |
| // Hence we need to use reflection to call a private method to |
| // initialize properly the HeadsetObjectsFactory.sInstance field. |
| Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting", |
| HeadsetObjectsFactory.class); |
| method.setAccessible(true); |
| method.invoke(null, mObjectsFactory); |
| doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); |
| doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| // This line must be called to make sure relevant objects are initialized properly |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| // Mock methods in AdapterService |
| doReturn(FAKE_HEADSET_UUID).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) |
| .getBondState(any(BluetoothDevice.class)); |
| doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); |
| doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); |
| doAnswer(invocation -> { |
| Set<BluetoothDevice> keys = mStateMachines.keySet(); |
| return keys.toArray(new BluetoothDevice[keys.size()]); |
| }).when(mAdapterService).getBondedDevices(); |
| // Mock system interface |
| doNothing().when(mSystemInterface).stop(); |
| when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState); |
| when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager); |
| when(mSystemInterface.isCallIdle()).thenReturn(true, false, true, false); |
| // Mock methods in HeadsetNativeInterface |
| mNativeInterface = spy(HeadsetNativeInterface.getInstance()); |
| doNothing().when(mNativeInterface).init(anyInt(), anyBoolean()); |
| doNothing().when(mNativeInterface).cleanup(); |
| doReturn(true).when(mNativeInterface).connectHfp(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectHfp(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).connectAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).disconnectAudio(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).sendBsir(any(BluetoothDevice.class), anyBoolean()); |
| // Mock methods in HeadsetObjectsFactory |
| doAnswer(invocation -> { |
| Assert.assertNotNull(mCurrentDevice); |
| final HeadsetStateMachine stateMachine = mock(HeadsetStateMachine.class); |
| doReturn(BluetoothProfile.STATE_DISCONNECTED).when(stateMachine).getConnectionState(); |
| doReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED).when(stateMachine).getAudioState(); |
| mStateMachines.put(mCurrentDevice, stateMachine); |
| return stateMachine; |
| }).when(mObjectsFactory).makeStateMachine(any(), any(), any(), any(), any(), any()); |
| doReturn(mSystemInterface).when(mObjectsFactory).makeSystemInterface(any()); |
| doReturn(mNativeInterface).when(mObjectsFactory).getNativeInterface(); |
| TestUtils.startService(mServiceRule, HeadsetService.class); |
| mHeadsetService = HeadsetService.getHeadsetService(); |
| Assert.assertNotNull(mHeadsetService); |
| verify(mObjectsFactory).makeSystemInterface(mHeadsetService); |
| verify(mObjectsFactory).getNativeInterface(); |
| mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder(); |
| Assert.assertNotNull(mHeadsetServiceBinder); |
| mHeadsetServiceBinder.setForceScoAudio(true, mAdapter.getAttributionSource()); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)) { |
| return; |
| } |
| TestUtils.stopService(mServiceRule, HeadsetService.class); |
| mHeadsetService = HeadsetService.getHeadsetService(); |
| Assert.assertNull(mHeadsetService); |
| mStateMachines.clear(); |
| mCurrentDevice = null; |
| Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting", |
| HeadsetObjectsFactory.class); |
| method.setAccessible(true); |
| method.invoke(null, (HeadsetObjectsFactory) null); |
| TestUtils.clearAdapterService(mAdapterService); |
| } |
| |
| /** |
| * Test to verify that HeadsetService can be successfully started |
| */ |
| @Test |
| public void testGetHeadsetService() { |
| Assert.assertEquals(mHeadsetService, HeadsetService.getHeadsetService()); |
| // Verify default connection and audio states |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, |
| mHeadsetService.getAudioState(mCurrentDevice)); |
| } |
| |
| /** |
| * Test okToAcceptConnection method using various test cases |
| */ |
| @Test |
| public void testOkToAcceptConnection() { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| int badPriorityValue = 1024; |
| int badBondState = 42; |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_NONE, badPriorityValue, |
| false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDING, badPriorityValue, |
| false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED, true); |
| testOkToAcceptConnectionCase(mCurrentDevice, BluetoothDevice.BOND_BONDED, badPriorityValue, |
| false); |
| testOkToAcceptConnectionCase(mCurrentDevice, badBondState, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, badBondState, |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, badBondState, |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToAcceptConnectionCase(mCurrentDevice, badBondState, badPriorityValue, false); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} returns true when the |
| * device was not connected and number of connected devices is less than |
| * {@link #MAX_HEADSET_CONNECTIONS} |
| */ |
| @Test |
| public void testConnectDevice_connectDeviceBelowLimit() { |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(Collections.singletonList(mCurrentDevice), |
| mHeadsetService.getConnectedDevices()); |
| // 2nd connection attempt will fail |
| Assert.assertFalse(mHeadsetService.connect(mCurrentDevice)); |
| // Verify makeStateMachine is only called once |
| verify(mObjectsFactory).makeStateMachine(any(), any(), any(), any(), any(), any()); |
| // Verify CONNECT is only sent once |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any()); |
| } |
| |
| /** |
| * Test that {@link HeadsetService#messageFromNative(HeadsetStackEvent)} will send correct |
| * message to the underlying state machine |
| */ |
| @Test |
| public void testMessageFromNative_deviceConnected() { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| // Test connect from native |
| HeadsetStackEvent connectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_CONNECTED, mCurrentDevice); |
| mHeadsetService.messageFromNative(connectedEvent); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT, |
| connectedEvent); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(Collections.singletonList(mCurrentDevice), |
| mHeadsetService.getConnectedDevices()); |
| // Test disconnect from native |
| HeadsetStackEvent disconnectEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mCurrentDevice); |
| mHeadsetService.messageFromNative(disconnectEvent); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT, |
| disconnectEvent); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices()); |
| } |
| |
| /** |
| * Stack connection event to {@link HeadsetHalConstants#CONNECTION_STATE_CONNECTING} should |
| * create new state machine |
| */ |
| @Test |
| public void testMessageFromNative_deviceConnectingUnknown() { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| HeadsetStackEvent connectingEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_CONNECTING, mCurrentDevice); |
| mHeadsetService.messageFromNative(connectingEvent); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.STACK_EVENT, |
| connectingEvent); |
| } |
| |
| /** |
| * Stack connection event to {@link HeadsetHalConstants#CONNECTION_STATE_DISCONNECTED} should |
| * crash by throwing {@link IllegalStateException} if the device is unknown |
| */ |
| @Test |
| public void testMessageFromNative_deviceDisconnectedUnknown() { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| HeadsetStackEvent connectingEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, mCurrentDevice); |
| try { |
| mHeadsetService.messageFromNative(connectingEvent); |
| Assert.fail("Expect an IllegalStateException"); |
| } catch (IllegalStateException exception) { |
| // Do nothing |
| } |
| verifyNoMoreInteractions(mObjectsFactory); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} fails after |
| * {@link #MAX_HEADSET_CONNECTIONS} connection requests |
| */ |
| @Test |
| public void testConnectDevice_connectDeviceAboveLimit() { |
| ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, |
| mAdapterService, mNativeInterface, mSystemInterface); |
| verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class), |
| eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService), |
| eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any(BluetoothDevice.class)); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| } |
| // Connect the next device will fail |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, MAX_HEADSET_CONNECTIONS); |
| Assert.assertFalse(mHeadsetService.connect(mCurrentDevice)); |
| // Though connection failed, a new state machine is still lazily created for the device |
| verify(mObjectsFactory, times(MAX_HEADSET_CONNECTIONS + 1)).makeStateMachine( |
| any(BluetoothDevice.class), eq(mHeadsetService.getStateMachinesThreadLooper()), |
| eq(mHeadsetService), eq(mAdapterService), eq(mNativeInterface), |
| eq(mSystemInterface)); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} return true when |
| * the device is connected and audio is not connected and returns false when audio is already |
| * connecting |
| */ |
| @Test |
| public void testConnectAudio_withOneDevice() { |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(Collections.singletonList(mCurrentDevice), |
| mHeadsetService.getConnectedDevices()); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); |
| // Test connect audio - set the device first as the active device |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| // 2nd connection attempt for the same device will succeed as well |
| Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice)); |
| // Verify CONNECT_AUDIO is only sent once |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| eq(HeadsetStateMachine.CONNECT_AUDIO), any()); |
| // Test disconnect audio |
| Assert.assertTrue(mHeadsetService.disconnectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| // Further disconnection requests will fail |
| Assert.assertFalse(mHeadsetService.disconnectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class)); |
| } |
| |
| /** |
| * Test to verify that HFP audio connection can be initiated when multiple devices are connected |
| * and can be canceled or disconnected as well |
| */ |
| @Test |
| public void testConnectAudio_withMultipleDevices() { |
| ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, |
| mAdapterService, mNativeInterface, mSystemInterface); |
| verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class), |
| eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService), |
| eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any(BluetoothDevice.class)); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Try to connect audio |
| // Should fail |
| Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice)); |
| // Should succeed after setActiveDevice() |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| HeadsetStateMachine.CONNECT_AUDIO, mCurrentDevice); |
| // Put device to audio connecting state |
| when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| // 2nd connection attempt will also succeed |
| Assert.assertTrue(mHeadsetService.connectAudio(mCurrentDevice)); |
| // Verify CONNECT_AUDIO is only sent once |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| eq(HeadsetStateMachine.CONNECT_AUDIO), any()); |
| // Put device to audio connected state |
| when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_CONNECTED); |
| // Disconnect audio |
| Assert.assertTrue(mHeadsetService.disconnectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| HeadsetStateMachine.DISCONNECT_AUDIO, mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| // Further disconnection requests will fail |
| Assert.assertFalse(mHeadsetService.disconnectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| eq(HeadsetStateMachine.DISCONNECT_AUDIO), any(BluetoothDevice.class)); |
| } |
| } |
| |
| /** |
| * Verify that only one device can be in audio connecting or audio connected state, further |
| * attempt to call {@link HeadsetService#connectAudio(BluetoothDevice)} should fail by returning |
| * false |
| */ |
| @Test |
| public void testConnectAudio_connectTwoAudioChannelsShouldFail() { |
| ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, |
| mAdapterService, mNativeInterface, mSystemInterface); |
| verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class), |
| eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService), |
| eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any(BluetoothDevice.class)); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| } |
| if (MAX_HEADSET_CONNECTIONS >= 2) { |
| // Try to connect audio |
| BluetoothDevice firstDevice = connectedDevices.get(0); |
| BluetoothDevice secondDevice = connectedDevices.get(1); |
| // Set the first device as the active device |
| Assert.assertTrue(mHeadsetService.setActiveDevice(firstDevice)); |
| Assert.assertTrue(mHeadsetService.connectAudio(firstDevice)); |
| verify(mStateMachines.get(firstDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO, |
| firstDevice); |
| // Put device to audio connecting state |
| when(mStateMachines.get(firstDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| // 2nd connection attempt will succeed for the same device |
| Assert.assertTrue(mHeadsetService.connectAudio(firstDevice)); |
| // Connect to 2nd device will fail |
| Assert.assertFalse(mHeadsetService.connectAudio(secondDevice)); |
| verify(mStateMachines.get(secondDevice), never()).sendMessage( |
| HeadsetStateMachine.CONNECT_AUDIO, secondDevice); |
| // Put device to audio connected state |
| when(mStateMachines.get(firstDevice).getAudioState()).thenReturn( |
| BluetoothHeadset.STATE_AUDIO_CONNECTED); |
| // Connect to 2nd device will fail |
| Assert.assertFalse(mHeadsetService.connectAudio(secondDevice)); |
| verify(mStateMachines.get(secondDevice), never()).sendMessage( |
| HeadsetStateMachine.CONNECT_AUDIO, secondDevice); |
| } |
| } |
| |
| /** |
| * Verify that {@link HeadsetService#connectAudio()} will connect to first connected/connecting |
| * device |
| */ |
| @Test |
| public void testConnectAudio_firstConnectedAudioDevice() { |
| ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| doAnswer(invocation -> { |
| BluetoothDevice[] devicesArray = new BluetoothDevice[connectedDevices.size()]; |
| return connectedDevices.toArray(devicesArray); |
| }).when(mAdapterService).getBondedDevices(); |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, |
| mAdapterService, mNativeInterface, mSystemInterface); |
| verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class), |
| eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService), |
| eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any(BluetoothDevice.class)); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); |
| } |
| // Try to connect audio |
| BluetoothDevice firstDevice = connectedDevices.get(0); |
| Assert.assertTrue(mHeadsetService.setActiveDevice(firstDevice)); |
| Assert.assertTrue(mHeadsetService.connectAudio()); |
| verify(mStateMachines.get(firstDevice)).sendMessage(HeadsetStateMachine.CONNECT_AUDIO, |
| firstDevice); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} fails if device |
| * was never connected |
| */ |
| @Test |
| public void testConnectAudio_deviceNeverConnected() { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice)); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connectAudio(BluetoothDevice)} fails if device |
| * is disconnected |
| */ |
| @Test |
| public void testConnectAudio_deviceDisconnected() { |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| HeadsetCallState headsetCallState = |
| new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING, |
| TEST_PHONE_NUMBER, 128, ""); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| // Put device in disconnected state |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertEquals(Collections.EMPTY_LIST, mHeadsetService.getConnectedDevices()); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); |
| // connectAudio should fail |
| Assert.assertFalse(mHeadsetService.connectAudio(mCurrentDevice)); |
| verify(mStateMachines.get(mCurrentDevice), never()).sendMessage( |
| eq(HeadsetStateMachine.CONNECT_AUDIO), any()); |
| } |
| |
| /** |
| * Verifies that phone state change will trigger a system-wide saving of call state even when |
| * no device is connected |
| * |
| * @throws RemoteException if binder call fails |
| */ |
| @Test |
| public void testPhoneStateChange_noDeviceSaveState() throws RemoteException { |
| HeadsetCallState headsetCallState = |
| new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING, |
| TEST_PHONE_NUMBER, 128, ""); |
| mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive, |
| headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber, |
| headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource()); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| verify(mAudioManager, never()).setParameters("A2dpSuspended=true"); |
| HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState, |
| ASYNC_CALL_TIMEOUT_MILLIS); |
| } |
| |
| /** |
| * Verifies that phone state change will trigger a system-wide saving of call state and send |
| * state change to connected devices |
| * |
| * @throws RemoteException if binder call fails |
| */ |
| @Test |
| public void testPhoneStateChange_oneDeviceSaveState() throws RemoteException { |
| HeadsetCallState headsetCallState = |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, |
| TEST_PHONE_NUMBER, 128, ""); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| // Connect one device |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); |
| // Change phone state |
| mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive, |
| headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber, |
| headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource()); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| |
| // Should not ask Audio HAL to suspend A2DP without active device |
| verify(mAudioManager, never()).setParameters("A2dpSuspended=true"); |
| // Make sure we notify device about this change |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState); |
| // Make sure state is updated once in phone state holder |
| HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState, |
| ASYNC_CALL_TIMEOUT_MILLIS); |
| |
| // Set the device first as the active device |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| // Change phone state |
| headsetCallState.mCallState = HeadsetHalConstants.CALL_STATE_ALERTING; |
| mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive, |
| headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber, |
| headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource()); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| // Ask Audio HAL to suspend A2DP |
| verify(mAudioManager).setParameters("A2dpSuspended=true"); |
| // Make sure state is updated |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage( |
| HeadsetStateMachine.CALL_STATE_CHANGED, headsetCallState); |
| verify(mPhoneState).setCallState(eq(headsetCallState.mCallState)); |
| } |
| |
| /** |
| * Verifies that phone state change will trigger a system-wide saving of call state and send |
| * state change to connected devices |
| * |
| * @throws RemoteException if binder call fails |
| */ |
| @Test |
| public void testPhoneStateChange_multipleDevicesSaveState() throws RemoteException { |
| HeadsetCallState headsetCallState = |
| new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_ALERTING, |
| TEST_PHONE_NUMBER, 128, ""); |
| final ArrayList<BluetoothDevice> connectedDevices = new ArrayList<>(); |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| verify(mObjectsFactory).makeStateMachine(mCurrentDevice, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, |
| mAdapterService, mNativeInterface, mSystemInterface); |
| verify(mObjectsFactory, times(i + 1)).makeStateMachine(any(BluetoothDevice.class), |
| eq(mHeadsetService.getStateMachinesThreadLooper()), eq(mHeadsetService), |
| eq(mAdapterService), eq(mNativeInterface), eq(mSystemInterface)); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(HeadsetStateMachine.CONNECT, |
| mCurrentDevice); |
| verify(mStateMachines.get(mCurrentDevice)).sendMessage(eq(HeadsetStateMachine.CONNECT), |
| any(BluetoothDevice.class)); |
| // Put device to connecting |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTING); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| // Put device to connected |
| connectedDevices.add(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn( |
| SystemClock.uptimeMillis()); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(mCurrentDevice)); |
| Assert.assertThat(mHeadsetService.getConnectedDevices(), |
| Matchers.containsInAnyOrder(connectedDevices.toArray())); |
| mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED); |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| } |
| // Change phone state |
| mHeadsetServiceBinder.phoneStateChanged(headsetCallState.mNumActive, |
| headsetCallState.mNumHeld, headsetCallState.mCallState, headsetCallState.mNumber, |
| headsetCallState.mType, headsetCallState.mName, mAdapter.getAttributionSource()); |
| // Ask Audio HAL to suspend A2DP |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| // Make sure we notify devices about this change |
| for (BluetoothDevice device : connectedDevices) { |
| verify(mStateMachines.get(device)).sendMessage(HeadsetStateMachine.CALL_STATE_CHANGED, |
| headsetCallState); |
| } |
| // Make sure state is updated once in phone state holder |
| HeadsetTestUtils.verifyPhoneStateChangeSetters(mPhoneState, headsetCallState, |
| ASYNC_CALL_TIMEOUT_MILLIS); |
| } |
| |
| /** |
| * Test that whether active device been removed after enable silence mode |
| */ |
| @Test |
| public void testSetSilenceMode() { |
| when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class), |
| eq(BluetoothProfile.HEADSET))) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| for (int i = 0; i < 2; i++) { |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, i); |
| Assert.assertTrue(mHeadsetService.connect(mCurrentDevice)); |
| when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice); |
| when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn( |
| BluetoothProfile.STATE_CONNECTED); |
| when(mStateMachines.get(mCurrentDevice).setSilenceDevice( |
| anyBoolean())).thenReturn(true); |
| } |
| mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0); |
| BluetoothDevice otherDevice = TestUtils.getTestDevice(mAdapter, 1); |
| |
| // Test whether active device been removed after enable silence mode. |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); |
| Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, true)); |
| Assert.assertNull(mHeadsetService.getActiveDevice()); |
| |
| // Test whether active device been resumed after disable silence mode. |
| Assert.assertTrue(mHeadsetService.setSilenceMode(mCurrentDevice, false)); |
| Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); |
| |
| // Test that active device should not be changed when silence a non-active device |
| Assert.assertTrue(mHeadsetService.setActiveDevice(mCurrentDevice)); |
| Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); |
| Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, true)); |
| Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); |
| |
| // Test that active device should not be changed when another device exits silence mode |
| Assert.assertTrue(mHeadsetService.setSilenceMode(otherDevice, false)); |
| Assert.assertEquals(mCurrentDevice, mHeadsetService.getActiveDevice()); |
| } |
| |
| /* |
| * Helper function to test okToAcceptConnection() method |
| * |
| * @param device test device |
| * @param bondState bond state value, could be invalid |
| * @param priority value, could be invalid, coudl be invalid |
| * @param expected expected result from okToAcceptConnection() |
| */ |
| private void testOkToAcceptConnectionCase(BluetoothDevice device, int bondState, int priority, |
| boolean expected) { |
| doReturn(bondState).when(mAdapterService).getBondState(device); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) |
| .thenReturn(priority); |
| Assert.assertEquals(expected, mHeadsetService.okToAcceptConnection(device)); |
| } |
| |
| } |