| /* |
| * 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.hamcrest.Matchers.*; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.*; |
| |
| import android.app.Activity; |
| import android.app.Instrumentation; |
| 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.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioManager; |
| import android.net.Uri; |
| import android.os.ParcelUuid; |
| import android.os.PowerManager; |
| import android.os.RemoteException; |
| import android.telecom.PhoneAccount; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.espresso.intent.Intents; |
| import androidx.test.espresso.intent.matcher.IntentMatchers; |
| 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.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.Spy; |
| |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.LinkedBlockingQueue; |
| |
| /** |
| * A set of integration test that involves both {@link HeadsetService} and |
| * {@link HeadsetStateMachine} |
| */ |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class HeadsetServiceAndStateMachineTest { |
| private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; |
| private static final int START_VR_TIMEOUT_MILLIS = 1000; |
| private static final int START_VR_TIMEOUT_WAIT_MILLIS = START_VR_TIMEOUT_MILLIS * 3 / 2; |
| private static final int MAX_HEADSET_CONNECTIONS = 5; |
| private static final ParcelUuid[] FAKE_HEADSET_UUID = {BluetoothUuid.HFP}; |
| private static final String TEST_PHONE_NUMBER = "1234567890"; |
| private static final String TEST_CALLER_ID = "Test Name"; |
| |
| @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); |
| |
| private Context mTargetContext; |
| private HeadsetService mHeadsetService; |
| private IBluetoothHeadset.Stub mHeadsetServiceBinder; |
| private BluetoothAdapter mAdapter; |
| private HeadsetNativeInterface mNativeInterface; |
| private ArgumentCaptor<HeadsetStateMachine> mStateMachineArgument = |
| ArgumentCaptor.forClass(HeadsetStateMachine.class); |
| private HashSet<BluetoothDevice> mBondedDevices = new HashSet<>(); |
| private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>(); |
| private final BlockingQueue<Intent> mActiveDeviceChangedQueue = new LinkedBlockingQueue<>(); |
| private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>(); |
| private HeadsetIntentReceiver mHeadsetIntentReceiver; |
| private int mOriginalVrTimeoutMs = 5000; |
| private PowerManager.WakeLock mVoiceRecognitionWakeLock; |
| |
| private class HeadsetIntentReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (action == null) { |
| Assert.fail("Action is null for intent " + intent); |
| return; |
| } |
| switch (action) { |
| case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: |
| try { |
| mConnectionStateChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Connection State Changed queue: " |
| + e.getMessage()); |
| } |
| break; |
| case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED: |
| try { |
| mActiveDeviceChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Active Device Changed queue: " |
| + e.getMessage()); |
| } |
| break; |
| case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED: |
| try { |
| mAudioStateChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Audio State Changed queue: " |
| + e.getMessage()); |
| } |
| break; |
| default: |
| Assert.fail("Unknown action " + action); |
| } |
| |
| } |
| } |
| |
| @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); |
| PowerManager powerManager = |
| (PowerManager) mTargetContext.getSystemService(Context.POWER_SERVICE); |
| mVoiceRecognitionWakeLock = |
| powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "VoiceRecognitionTest"); |
| TestUtils.setAdapterService(mAdapterService); |
| doReturn(MAX_HEADSET_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices(); |
| doReturn(new ParcelUuid[]{BluetoothUuid.HFP}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); |
| // 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); |
| // 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)); |
| doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when( |
| mAdapterService).getBondedDevices(); |
| // Mock system interface |
| doNothing().when(mSystemInterface).init(); |
| doNothing().when(mSystemInterface).stop(); |
| when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState); |
| when(mSystemInterface.getAudioManager()).thenReturn(mAudioManager); |
| when(mSystemInterface.activateVoiceRecognition()).thenReturn(true); |
| when(mSystemInterface.deactivateVoiceRecognition()).thenReturn(true); |
| when(mSystemInterface.getVoiceRecognitionWakeLock()).thenReturn(mVoiceRecognitionWakeLock); |
| when(mSystemInterface.isCallIdle()).thenReturn(true); |
| // 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()); |
| doReturn(true).when(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface).stopVoiceRecognition(any(BluetoothDevice.class)); |
| doReturn(true).when(mNativeInterface) |
| .atResponseCode(any(BluetoothDevice.class), anyInt(), anyInt()); |
| // Use real state machines here |
| doCallRealMethod().when(mObjectsFactory) |
| .makeStateMachine(any(), any(), any(), any(), any(), any()); |
| // Mock methods in HeadsetObjectsFactory |
| doReturn(mSystemInterface).when(mObjectsFactory).makeSystemInterface(any()); |
| doReturn(mNativeInterface).when(mObjectsFactory).getNativeInterface(); |
| Intents.init(); |
| // Modify start VR timeout to a smaller value for testing |
| mOriginalVrTimeoutMs = HeadsetService.sStartVrTimeoutMs; |
| HeadsetService.sStartVrTimeoutMs = START_VR_TIMEOUT_MILLIS; |
| TestUtils.startService(mServiceRule, HeadsetService.class); |
| mHeadsetService = HeadsetService.getHeadsetService(); |
| Assert.assertNotNull(mHeadsetService); |
| verify(mObjectsFactory).makeSystemInterface(mHeadsetService); |
| verify(mObjectsFactory).getNativeInterface(); |
| verify(mNativeInterface).init(MAX_HEADSET_CONNECTIONS + 1, true /* inband ringtone */); |
| mHeadsetServiceBinder = (IBluetoothHeadset.Stub) mHeadsetService.initBinder(); |
| Assert.assertNotNull(mHeadsetServiceBinder); |
| |
| // Set up the Connection State Changed receiver |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); |
| filter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); |
| filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); |
| mHeadsetIntentReceiver = new HeadsetIntentReceiver(); |
| mTargetContext.registerReceiver(mHeadsetIntentReceiver, filter); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_hs_hfp)) { |
| return; |
| } |
| mTargetContext.unregisterReceiver(mHeadsetIntentReceiver); |
| TestUtils.stopService(mServiceRule, HeadsetService.class); |
| HeadsetService.sStartVrTimeoutMs = mOriginalVrTimeoutMs; |
| Intents.release(); |
| mHeadsetService = HeadsetService.getHeadsetService(); |
| Assert.assertNull(mHeadsetService); |
| Method method = HeadsetObjectsFactory.class.getDeclaredMethod("setInstanceForTesting", |
| HeadsetObjectsFactory.class); |
| method.setAccessible(true); |
| method.invoke(null, (HeadsetObjectsFactory) null); |
| TestUtils.clearAdapterService(mAdapterService); |
| mBondedDevices.clear(); |
| mConnectionStateChangedQueue.clear(); |
| mActiveDeviceChangedQueue.clear(); |
| // Clear classes that is spied on and has static life time |
| clearInvocations(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify that HeadsetService can be successfully started |
| */ |
| @Test |
| public void testGetHeadsetService() { |
| Assert.assertEquals(mHeadsetService, HeadsetService.getHeadsetService()); |
| // Verify default connection and audio states |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mHeadsetService.getConnectionState(device)); |
| Assert.assertEquals(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, |
| mHeadsetService.getAudioState(device)); |
| } |
| |
| /** |
| * Test to verify that {@link HeadsetService#connect(BluetoothDevice)} actually result in a |
| * call to native interface to create HFP |
| */ |
| @Test |
| public void testConnectFromApi() { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mBondedDevices.add(device); |
| Assert.assertTrue(mHeadsetService.connect(device)); |
| verify(mObjectsFactory).makeStateMachine(device, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); |
| verify(mNativeInterface).connectHfp(device); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mHeadsetService.getConnectionState(device)); |
| Assert.assertEquals(Collections.singletonList(device), |
| mHeadsetService.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTING})); |
| // Get feedback from native to put device into connected state |
| HeadsetStackEvent connectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device); |
| mHeadsetService.messageFromNative(connectedEvent); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(device)); |
| Assert.assertEquals(Collections.singletonList(device), |
| mHeadsetService.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED})); |
| } |
| |
| /** |
| * Test to verify that {@link BluetoothDevice#ACTION_BOND_STATE_CHANGED} intent with |
| * {@link BluetoothDevice#EXTRA_BOND_STATE} as {@link BluetoothDevice#BOND_NONE} will cause a |
| * disconnected device to be removed from state machine map |
| */ |
| @Test |
| public void testUnbondDevice_disconnectBeforeUnbond() { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mBondedDevices.add(device); |
| Assert.assertTrue(mHeadsetService.connect(device)); |
| verify(mObjectsFactory).makeStateMachine(device, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); |
| verify(mNativeInterface).connectHfp(device); |
| // Get feedback from native layer to go back to disconnected state |
| HeadsetStackEvent connectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, device); |
| mHeadsetService.messageFromNative(connectedEvent); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTING); |
| // Send unbond intent |
| doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService).getBondState(eq(device)); |
| Intent unbondIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); |
| unbondIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); |
| unbondIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| InstrumentationRegistry.getTargetContext().sendBroadcast(unbondIntent); |
| // Check that the state machine is actually destroyed |
| verify(mObjectsFactory, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).destroyStateMachine( |
| mStateMachineArgument.capture()); |
| Assert.assertEquals(device, mStateMachineArgument.getValue().getDevice()); |
| } |
| |
| /** |
| * Test to verify that if a device can be property disconnected after |
| * {@link BluetoothDevice#ACTION_BOND_STATE_CHANGED} intent with |
| * {@link BluetoothDevice#EXTRA_BOND_STATE} as {@link BluetoothDevice#BOND_NONE} is received. |
| */ |
| @Test |
| public void testUnbondDevice_disconnectAfterUnbond() { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| mBondedDevices.add(device); |
| Assert.assertTrue(mHeadsetService.connect(device)); |
| verify(mObjectsFactory).makeStateMachine(device, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| // 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 |
| verify(mNativeInterface, after(ASYNC_CALL_TIMEOUT_MILLIS)).connectHfp(device); |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); |
| // Get feedback from native layer to go to connected state |
| HeadsetStackEvent connectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device); |
| mHeadsetService.messageFromNative(connectedEvent); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(device)); |
| Assert.assertEquals(Collections.singletonList(device), |
| mHeadsetService.getConnectedDevices()); |
| // Send unbond intent |
| doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService).getBondState(eq(device)); |
| Intent unbondIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); |
| unbondIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); |
| unbondIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| InstrumentationRegistry.getTargetContext().sendBroadcast(unbondIntent); |
| // Check that the state machine is not destroyed |
| verify(mObjectsFactory, after(ASYNC_CALL_TIMEOUT_MILLIS).never()).destroyStateMachine( |
| any()); |
| // Now disconnect the device |
| HeadsetStackEvent connectingEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_DISCONNECTED, device); |
| mHeadsetService.messageFromNative(connectingEvent); |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED); |
| // Check that the state machine is destroyed after another async call |
| verify(mObjectsFactory, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).destroyStateMachine( |
| mStateMachineArgument.capture()); |
| Assert.assertEquals(device, mStateMachineArgument.getValue().getDevice()); |
| |
| } |
| |
| /** |
| * Test the functionality of |
| * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and |
| * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()} |
| * |
| * Normal start and stop |
| */ |
| @Test |
| public void testVirtualCall_normalStartStop() throws RemoteException { |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2); |
| Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice)); |
| verify(mNativeInterface).setActiveDevice(activeDevice); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); |
| Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Start virtual call |
| Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall()); |
| Assert.assertTrue(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStartSequenceInvocations(connectedDevices); |
| // End virtual call |
| Assert.assertTrue(mHeadsetServiceBinder.stopScoUsingVirtualVoiceCall()); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStopSequenceInvocations(connectedDevices); |
| } |
| |
| /** |
| * Test the functionality of |
| * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and |
| * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()} |
| * |
| * Virtual call should be preempted by telecom call |
| */ |
| @Test |
| public void testVirtualCall_preemptedByTelecomCall() throws RemoteException { |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2); |
| Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice)); |
| verify(mNativeInterface).setActiveDevice(activeDevice); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); |
| Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Start virtual call |
| Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall()); |
| Assert.assertTrue(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStartSequenceInvocations(connectedDevices); |
| // Virtual call should be preempted by telecom call |
| mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING, |
| TEST_PHONE_NUMBER, 128, ""); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStopSequenceInvocations(connectedDevices); |
| verifyCallStateToNativeInvocation( |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING, |
| TEST_PHONE_NUMBER, 128, ""), connectedDevices); |
| } |
| |
| /** |
| * Test the functionality of |
| * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} and |
| * {@link BluetoothHeadset#stopScoUsingVirtualVoiceCall()} |
| * |
| * Virtual call should be rejected when there is a telecom call |
| */ |
| @Test |
| public void testVirtualCall_rejectedWhenThereIsTelecomCall() throws RemoteException { |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| BluetoothDevice activeDevice = connectedDevices.get(MAX_HEADSET_CONNECTIONS / 2); |
| Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice)); |
| verify(mNativeInterface).setActiveDevice(activeDevice); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); |
| Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Reject virtual call setup if call state is not idle |
| when(mSystemInterface.isCallIdle()).thenReturn(false); |
| Assert.assertFalse(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall()); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| } |
| |
| /** |
| * Test the behavior when dialing outgoing call from the headset |
| */ |
| @Test |
| public void testDialingOutCall_NormalDialingOut() throws RemoteException { |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| BluetoothDevice activeDevice = connectedDevices.get(0); |
| Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice)); |
| verify(mNativeInterface).setActiveDevice(activeDevice); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); |
| Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Try dialing out from the a non active Headset |
| BluetoothDevice dialingOutDevice = connectedDevices.get(1); |
| HeadsetStackEvent dialingOutEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER, |
| dialingOutDevice); |
| Uri dialOutUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, TEST_PHONE_NUMBER, null); |
| Instrumentation.ActivityResult result = |
| new Instrumentation.ActivityResult(Activity.RESULT_OK, null); |
| Intents.intending(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED)) |
| .respondWith(result); |
| mHeadsetService.messageFromNative(dialingOutEvent); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, dialingOutDevice); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| Assert.assertTrue(mHeadsetService.hasDeviceInitiatedDialingOut()); |
| // Make sure the correct intent is fired |
| Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED), |
| IntentMatchers.hasData(dialOutUri)), Intents.times(1)); |
| // Further dial out attempt from same device will fail |
| mHeadsetService.messageFromNative(dialingOutEvent); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| verify(mNativeInterface).atResponseCode(dialingOutDevice, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| // Further dial out attempt from other device will fail |
| HeadsetStackEvent dialingOutEventOtherDevice = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER, |
| activeDevice); |
| mHeadsetService.messageFromNative(dialingOutEventOtherDevice); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| verify(mNativeInterface).atResponseCode(activeDevice, HeadsetHalConstants.AT_RESPONSE_ERROR, |
| 0); |
| TestUtils.waitForNoIntent(ASYNC_CALL_TIMEOUT_MILLIS, mActiveDeviceChangedQueue); |
| Assert.assertEquals(dialingOutDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Make sure only one intent is fired |
| Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED), |
| IntentMatchers.hasData(dialOutUri)), Intents.times(1)); |
| // Verify that phone state update confirms the dial out event |
| mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, |
| TEST_PHONE_NUMBER, 128, ""); |
| HeadsetCallState dialingCallState = |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, |
| TEST_PHONE_NUMBER, 128, ""); |
| verifyCallStateToNativeInvocation(dialingCallState, connectedDevices); |
| verify(mNativeInterface).atResponseCode(dialingOutDevice, |
| HeadsetHalConstants.AT_RESPONSE_OK, 0); |
| // Verify that IDLE phone state clears the dialing out flag |
| mHeadsetServiceBinder.phoneStateChanged(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, |
| TEST_PHONE_NUMBER, 128, ""); |
| HeadsetCallState activeCallState = |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, |
| TEST_PHONE_NUMBER, 128, ""); |
| verifyCallStateToNativeInvocation(activeCallState, connectedDevices); |
| Assert.assertFalse(mHeadsetService.hasDeviceInitiatedDialingOut()); |
| } |
| |
| /** |
| * Test the behavior when dialing outgoing call from the headset |
| */ |
| @Test |
| public void testDialingOutCall_DialingOutPreemptVirtualCall() throws RemoteException { |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| BluetoothDevice activeDevice = connectedDevices.get(0); |
| Assert.assertTrue(mHeadsetServiceBinder.setActiveDevice(activeDevice)); |
| verify(mNativeInterface).setActiveDevice(activeDevice); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, activeDevice); |
| Assert.assertEquals(activeDevice, mHeadsetServiceBinder.getActiveDevice()); |
| // Start virtual call |
| Assert.assertTrue(mHeadsetServiceBinder.startScoUsingVirtualVoiceCall()); |
| Assert.assertTrue(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStartSequenceInvocations(connectedDevices); |
| // Try dialing out from the a non active Headset |
| BluetoothDevice dialingOutDevice = connectedDevices.get(1); |
| HeadsetStackEvent dialingOutEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, TEST_PHONE_NUMBER, |
| dialingOutDevice); |
| Uri dialOutUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, TEST_PHONE_NUMBER, null); |
| Instrumentation.ActivityResult result = |
| new Instrumentation.ActivityResult(Activity.RESULT_OK, null); |
| Intents.intending(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED)) |
| .respondWith(result); |
| mHeadsetService.messageFromNative(dialingOutEvent); |
| waitAndVerifyActiveDeviceChangedIntent(ASYNC_CALL_TIMEOUT_MILLIS, dialingOutDevice); |
| TestUtils.waitForLooperToFinishScheduledTask( |
| mHeadsetService.getStateMachinesThreadLooper()); |
| Assert.assertTrue(mHeadsetService.hasDeviceInitiatedDialingOut()); |
| // Make sure the correct intent is fired |
| Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_CALL_PRIVILEGED), |
| IntentMatchers.hasData(dialOutUri)), Intents.times(1)); |
| // Virtual call should be preempted by dialing out call |
| Assert.assertFalse(mHeadsetService.isVirtualCallStarted()); |
| verifyVirtualCallStopSequenceInvocations(connectedDevices); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding active HF initiated voice recognition |
| * in the successful scenario |
| * 1. HF device sends AT+BVRA=1 |
| * 2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND} |
| * 3. AG call {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} to indicate |
| * that voice recognition has stopped |
| * 4. AG sends OK to HF |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleHfInitiatedSuccess() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| startVoiceRecognitionFromHf(device); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding active HF stop voice recognition |
| * in the successful scenario |
| * 1. HF device sends AT+BVRA=0 |
| * 2. Let voice recognition app to stop |
| * 3. AG respond with OK |
| * 4. Disconnect audio |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleHfStopSuccess() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| startVoiceRecognitionFromHf(device); |
| // Stop voice recognition |
| HeadsetStackEvent stopVrEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STOPPED, device); |
| mHeadsetService.messageFromNative(stopVrEvent); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).deactivateVoiceRecognition(); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS).times(2)).atResponseCode(device, |
| HeadsetHalConstants.AT_RESPONSE_OK, 0); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(device); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding active HF initiated voice recognition |
| * in the failed to activate scenario |
| * 1. HF device sends AT+BVRA=1 |
| * 2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND} |
| * 3. Failed to activate voice recognition through intent |
| * 4. AG sends ERROR to HF |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleHfInitiatedFailedToActivate() { |
| when(mSystemInterface.activateVoiceRecognition()).thenReturn(false); |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| HeadsetStackEvent startVrEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, device); |
| mHeadsetService.messageFromNative(startVrEvent); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(device, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| verifyNoMoreInteractions(mNativeInterface); |
| verifyZeroInteractions(mAudioManager); |
| } |
| |
| |
| /** |
| * Test to verify the following behavior regarding active HF initiated voice recognition |
| * in the timeout scenario |
| * 1. HF device sends AT+BVRA=1 |
| * 2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND} |
| * 3. AG failed to get back to us on time |
| * 4. AG sends ERROR to HF |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleHfInitiatedTimeout() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| HeadsetStackEvent startVrEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, device); |
| mHeadsetService.messageFromNative(startVrEvent); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); |
| verify(mNativeInterface, timeout(START_VR_TIMEOUT_WAIT_MILLIS)).atResponseCode(device, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| verifyNoMoreInteractions(mNativeInterface); |
| verifyZeroInteractions(mAudioManager); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the successful scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started |
| * 2. AG send +BVRA:1 to HF |
| * 3. AG start SCO connection if SCO has not been started |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleAgInitiatedSuccess() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| startVoiceRecognitionFromAg(); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the successful scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started, BluetoothDevice is null in this case |
| * 2. AG send +BVRA:1 to current active HF |
| * 3. AG start SCO connection if SCO has not been started |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleAgInitiatedSuccessNullInput() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition on null argument should go to active device |
| Assert.assertTrue(mHeadsetService.startVoiceRecognition(null)); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the successful scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started, BluetoothDevice is null and active device is null |
| * 2. The call should fail |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleAgInitiatedFailNullActiveDevice() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(null)); |
| // TODO(b/79760385): setActiveDevice(null) does not propagate to native layer |
| // verify(mNativeInterface).setActiveDevice(null); |
| Assert.assertNull(mHeadsetService.getActiveDevice()); |
| // Start voice recognition on null argument should fail |
| Assert.assertFalse(mHeadsetService.startVoiceRecognition(null)); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG stops voice recognition |
| * in the successful scenario |
| * 1. AG stops voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has stopped |
| * 2. AG send +BVRA:0 to HF |
| * 3. AG stop SCO connection |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleAgStopSuccess() { |
| // Connect HF |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(device); |
| // Make device active |
| Assert.assertTrue(mHeadsetService.setActiveDevice(device)); |
| verify(mNativeInterface).setActiveDevice(device); |
| Assert.assertEquals(device, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| startVoiceRecognitionFromAg(); |
| // Stop voice recognition |
| Assert.assertTrue(mHeadsetService.stopVoiceRecognition(device)); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).stopVoiceRecognition(device); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(device); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the device not connected failure scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started |
| * 2. Device is not connected, return false |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_SingleAgInitiatedDeviceNotConnected() { |
| // Start voice recognition |
| BluetoothDevice disconnectedDevice = TestUtils.getTestDevice(mAdapter, 0); |
| Assert.assertFalse(mHeadsetService.startVoiceRecognition(disconnectedDevice)); |
| verifyNoMoreInteractions(mNativeInterface); |
| verifyZeroInteractions(mAudioManager); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding non active HF initiated voice recognition |
| * in the successful scenario |
| * 1. HF device sends AT+BVRA=1 |
| * 2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND} |
| * 3. AG call {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate |
| * that voice recognition has started |
| * 4. AG sends OK to HF |
| * 5. Suspend A2DP |
| * 6. Start SCO if SCO hasn't been started |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_MultiHfInitiatedSwitchActiveDeviceSuccess() { |
| // Connect two devices |
| BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(deviceA); |
| BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1); |
| connectTestDevice(deviceB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false); |
| // Set active device to device B |
| Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB)); |
| verify(mNativeInterface).setActiveDevice(deviceB); |
| Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice()); |
| // Start voice recognition from non active device A |
| HeadsetStackEvent startVrEventA = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, deviceA); |
| mHeadsetService.messageFromNative(startVrEventA); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); |
| // Active device should have been swapped to device A |
| verify(mNativeInterface).setActiveDevice(deviceA); |
| Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice()); |
| // Start voice recognition from other device should fail |
| HeadsetStackEvent startVrEventB = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, deviceB); |
| mHeadsetService.messageFromNative(startVrEventB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceB, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| // Reply to continue voice recognition |
| mHeadsetService.startVoiceRecognition(deviceA); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA, |
| HeadsetHalConstants.AT_RESPONSE_OK, 0); |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding non active HF initiated voice recognition |
| * in the successful scenario |
| * 1. HF device sends AT+BVRA=1 |
| * 2. HeadsetStateMachine sends out {@link Intent#ACTION_VOICE_COMMAND} |
| * 3. AG call {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate |
| * that voice recognition has started, but on a wrong HF |
| * 4. Headset service instead keep using the initiating HF |
| * 5. AG sends OK to HF |
| * 6. Suspend A2DP |
| * 7. Start SCO if SCO hasn't been started |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_MultiHfInitiatedSwitchActiveDeviceReplyWrongHfSuccess() { |
| // Connect two devices |
| BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(deviceA); |
| BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1); |
| connectTestDevice(deviceB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false); |
| // Set active device to device B |
| Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB)); |
| verify(mNativeInterface).setActiveDevice(deviceB); |
| Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice()); |
| // Start voice recognition from non active device A |
| HeadsetStackEvent startVrEventA = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, deviceA); |
| mHeadsetService.messageFromNative(startVrEventA); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); |
| // Active device should have been swapped to device A |
| verify(mNativeInterface).setActiveDevice(deviceA); |
| Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice()); |
| // Start voice recognition from other device should fail |
| HeadsetStackEvent startVrEventB = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, deviceB); |
| mHeadsetService.messageFromNative(startVrEventB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceB, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| // Reply to continue voice recognition on a wrong device |
| mHeadsetService.startVoiceRecognition(deviceB); |
| // We still continue on the initiating HF |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA, |
| HeadsetHalConstants.AT_RESPONSE_OK, 0); |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the successful scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started |
| * 2. Suspend A2DP |
| * 3. AG send +BVRA:1 to HF |
| * 4. AG start SCO connection if SCO has not been started |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_MultiAgInitiatedSuccess() { |
| // Connect two devices |
| BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(deviceA); |
| BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1); |
| connectTestDevice(deviceB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false); |
| // Set active device to device B |
| Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB)); |
| verify(mNativeInterface).setActiveDevice(deviceB); |
| Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice()); |
| // Start voice recognition |
| startVoiceRecognitionFromAg(); |
| // Start voice recognition from other device should fail |
| HeadsetStackEvent startVrEventA = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, deviceA); |
| mHeadsetService.messageFromNative(startVrEventA); |
| // TODO(b/79660380): Workaround in case voice recognition was not terminated properly |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).stopVoiceRecognition(deviceB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).disconnectAudio(deviceB); |
| // This request should still fail |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(deviceA, |
| HeadsetHalConstants.AT_RESPONSE_ERROR, 0); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify the following behavior regarding AG initiated voice recognition |
| * in the device not active failure scenario |
| * 1. AG starts voice recognition and notify the Bluetooth stack via |
| * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} to indicate that voice |
| * recognition has started |
| * 2. Device is not active, should do voice recognition on active device only |
| * |
| * Reference: Section 4.25, Page 64/144 of HFP 1.7.1 specification |
| */ |
| @Test |
| public void testVoiceRecognition_MultiAgInitiatedDeviceNotActive() { |
| // Connect two devices |
| BluetoothDevice deviceA = TestUtils.getTestDevice(mAdapter, 0); |
| connectTestDevice(deviceA); |
| BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1); |
| connectTestDevice(deviceB); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false); |
| // Set active device to device B |
| Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB)); |
| verify(mNativeInterface).setActiveDevice(deviceB); |
| Assert.assertEquals(deviceB, mHeadsetService.getActiveDevice()); |
| // Start voice recognition should succeed |
| Assert.assertTrue(mHeadsetService.startVoiceRecognition(deviceA)); |
| verify(mNativeInterface).setActiveDevice(deviceA); |
| Assert.assertEquals(deviceA, mHeadsetService.getActiveDevice()); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(deviceA); |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(deviceA); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, deviceA, |
| BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| mHeadsetService.messageFromNative( |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, |
| HeadsetHalConstants.AUDIO_STATE_CONNECTED, deviceA)); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, deviceA, |
| BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| /** |
| * Test to verify the call state and caller information are correctly delivered |
| * {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int, String, boolean)} |
| */ |
| @Test |
| public void testPhoneStateChangedWithIncomingCallState() throws RemoteException { |
| // Connect HF |
| for (int i = 0; i < MAX_HEADSET_CONNECTIONS; ++i) { |
| BluetoothDevice device = TestUtils.getTestDevice(mAdapter, i); |
| connectTestDevice(device); |
| Assert.assertThat(mHeadsetServiceBinder.getConnectedDevices(), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| Assert.assertThat(mHeadsetServiceBinder.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTED}), |
| Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| } |
| List<BluetoothDevice> connectedDevices = mHeadsetServiceBinder.getConnectedDevices(); |
| Assert.assertThat(connectedDevices, Matchers.containsInAnyOrder(mBondedDevices.toArray())); |
| // Incoming call update by telecom |
| mHeadsetServiceBinder.phoneStateChanged(0, 0, HeadsetHalConstants.CALL_STATE_INCOMING, |
| TEST_PHONE_NUMBER, 128, TEST_CALLER_ID); |
| HeadsetCallState incomingCallState = new HeadsetCallState(0, 0, |
| HeadsetHalConstants.CALL_STATE_INCOMING, TEST_PHONE_NUMBER, 128, TEST_CALLER_ID); |
| verifyCallStateToNativeInvocation(incomingCallState, connectedDevices); |
| } |
| |
| private void startVoiceRecognitionFromHf(BluetoothDevice device) { |
| // Start voice recognition |
| HeadsetStackEvent startVrEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, |
| HeadsetHalConstants.VR_STATE_STARTED, device); |
| mHeadsetService.messageFromNative(startVrEvent); |
| verify(mSystemInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).activateVoiceRecognition(); |
| Assert.assertTrue(mHeadsetService.startVoiceRecognition(device)); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseCode(device, |
| HeadsetHalConstants.AT_RESPONSE_OK, 0); |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(device); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| mHeadsetService.messageFromNative( |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, |
| HeadsetHalConstants.AUDIO_STATE_CONNECTED, device)); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| private void startVoiceRecognitionFromAg() { |
| BluetoothDevice device = mHeadsetService.getActiveDevice(); |
| Assert.assertNotNull(device); |
| Assert.assertTrue(mHeadsetService.startVoiceRecognition(device)); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).startVoiceRecognition(device); |
| verify(mAudioManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS)) |
| .setParameters("A2dpSuspended=true"); |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).connectAudio(device); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothHeadset.STATE_AUDIO_CONNECTING, BluetoothHeadset.STATE_AUDIO_DISCONNECTED); |
| mHeadsetService.messageFromNative( |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, |
| HeadsetHalConstants.AUDIO_STATE_CONNECTED, device)); |
| waitAndVerifyAudioStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTING); |
| verifyNoMoreInteractions(mNativeInterface); |
| } |
| |
| private void connectTestDevice(BluetoothDevice device) { |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.HEADSET)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| // Make device bonded |
| mBondedDevices.add(device); |
| // Use connecting event to indicate that device is connecting |
| HeadsetStackEvent rfcommConnectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_CONNECTED, device); |
| mHeadsetService.messageFromNative(rfcommConnectedEvent); |
| verify(mObjectsFactory).makeStateMachine(device, |
| mHeadsetService.getStateMachinesThreadLooper(), mHeadsetService, mAdapterService, |
| mNativeInterface, mSystemInterface); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mHeadsetService.getConnectionState(device)); |
| Assert.assertEquals(Collections.singletonList(device), |
| mHeadsetService.getDevicesMatchingConnectionStates( |
| new int[]{BluetoothProfile.STATE_CONNECTING})); |
| // Get feedback from native to put device into connected state |
| HeadsetStackEvent slcConnectedEvent = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, |
| HeadsetHalConstants.CONNECTION_STATE_SLC_CONNECTED, device); |
| mHeadsetService.messageFromNative(slcConnectedEvent); |
| // 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 |
| waitAndVerifyConnectionStateIntent(ASYNC_CALL_TIMEOUT_MILLIS, device, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mHeadsetService.getConnectionState(device)); |
| } |
| |
| private void waitAndVerifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, |
| int newState, int prevState) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mConnectionStateChangedQueue); |
| Assert.assertNotNull(intent); |
| HeadsetTestUtils.verifyConnectionStateBroadcast(device, newState, prevState, intent, false); |
| } |
| |
| private void waitAndVerifyActiveDeviceChangedIntent(int timeoutMs, BluetoothDevice device) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mActiveDeviceChangedQueue); |
| Assert.assertNotNull(intent); |
| HeadsetTestUtils.verifyActiveDeviceChangedBroadcast(device, intent, false); |
| } |
| |
| private void waitAndVerifyAudioStateIntent(int timeoutMs, BluetoothDevice device, int newState, |
| int prevState) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mAudioStateChangedQueue); |
| Assert.assertNotNull(intent); |
| HeadsetTestUtils.verifyAudioStateBroadcast(device, newState, prevState, intent); |
| } |
| |
| /** |
| * Verify the series of invocations after |
| * {@link BluetoothHeadset#startScoUsingVirtualVoiceCall()} |
| * |
| * @param connectedDevices must be in the same sequence as |
| * {@link BluetoothHeadset#getConnectedDevices()} |
| */ |
| private void verifyVirtualCallStartSequenceInvocations(List<BluetoothDevice> connectedDevices) { |
| // Do not verify HeadsetPhoneState changes as it is verified in HeadsetServiceTest |
| verifyCallStateToNativeInvocation( |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_DIALING, "", 0, ""), |
| connectedDevices); |
| verifyCallStateToNativeInvocation( |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_ALERTING, "", 0, ""), |
| connectedDevices); |
| verifyCallStateToNativeInvocation( |
| new HeadsetCallState(1, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""), |
| connectedDevices); |
| } |
| |
| private void verifyVirtualCallStopSequenceInvocations(List<BluetoothDevice> connectedDevices) { |
| verifyCallStateToNativeInvocation( |
| new HeadsetCallState(0, 0, HeadsetHalConstants.CALL_STATE_IDLE, "", 0, ""), |
| connectedDevices); |
| } |
| |
| private void verifyCallStateToNativeInvocation(HeadsetCallState headsetCallState, |
| List<BluetoothDevice> connectedDevices) { |
| for (BluetoothDevice device : connectedDevices) { |
| verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).phoneStateChange(device, |
| headsetCallState); |
| } |
| } |
| } |