blob: dc8683db0daaed0a3a7a26ac88e856861580dfea [file] [log] [blame]
/*
* Copyright 2016 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.hfpclient;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.ENTER_PRIVATE_MODE;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_START;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.VOICE_RECOGNITION_STOP;
import static org.mockito.Mockito.*;
import android.app.BroadcastOptions;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.matcher.IntentMatchers;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.hfp.HeadsetStackEvent;
import org.hamcrest.core.AllOf;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.hamcrest.MockitoHamcrest;
import java.util.List;
import java.util.Set;
/**
* Test cases for {@link HeadsetClientStateMachine}.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class HeadsetClientStateMachineTest {
private BluetoothAdapter mAdapter;
private HandlerThread mHandlerThread;
private TestHeadsetClientStateMachine mHeadsetClientStateMachine;
private BluetoothDevice mTestDevice;
private Context mTargetContext;
@Mock
private AdapterService mAdapterService;
@Mock
private Resources mMockHfpResources;
@Mock
private HeadsetService mHeadsetService;
@Mock
private HeadsetClientService mHeadsetClientService;
@Mock
private AudioManager mAudioManager;
private NativeInterface mNativeInterface;
private static final int STANDARD_WAIT_MILLIS = 1000;
private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000;
private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS
* 3 / 2;
private static final int TIMEOUT_MS = 1000;
@Before
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when HeadsetClientService is not enabled",
HeadsetClientService.isEnabled());
// Setup mocks and test assets
MockitoAnnotations.initMocks(this);
// Set a valid volume
when(mAudioManager.getStreamVolume(anyInt())).thenReturn(2);
when(mAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
when(mAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);
when(mHeadsetClientService.getAudioManager()).thenReturn(
mAudioManager);
when(mHeadsetClientService.getResources()).thenReturn(mMockHfpResources);
when(mMockHfpResources.getBoolean(R.bool.hfp_clcc_poll_during_call)).thenReturn(true);
when(mMockHfpResources.getInteger(R.integer.hfp_clcc_poll_interval_during_call))
.thenReturn(2000);
TestUtils.setAdapterService(mAdapterService);
mNativeInterface = spy(NativeInterface.getInstance());
doReturn(true).when(mNativeInterface).sendAndroidAt(anyObject(), anyString());
// This line must be called to make sure relevant objects are initialized properly
mAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
// Setup thread and looper
mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread");
mHandlerThread.start();
// Manage looper execution in main test thread explicitly to guarantee timing consistency
mHeadsetClientStateMachine = new TestHeadsetClientStateMachine(mHeadsetClientService,
mHeadsetService, mHandlerThread.getLooper(), mNativeInterface);
mHeadsetClientStateMachine.start();
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
}
@After
public void tearDown() throws Exception {
if (!HeadsetClientService.isEnabled()) {
return;
}
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mHeadsetClientStateMachine.allowConnect = null;
mHeadsetClientStateMachine.doQuit();
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mHandlerThread.quit();
TestUtils.clearAdapterService(mAdapterService);
verifyNoMoreInteractions(mHeadsetService);
}
/**
* Test that default state is disconnected
*/
@SmallTest
@Test
public void testDefaultDisconnectedState() {
Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(null),
BluetoothProfile.STATE_DISCONNECTED);
}
/**
* Test that an incoming connection with low priority is rejected
*/
@MediumTest
@Test
public void testIncomingPriorityReject() {
// Return false for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
connStCh.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
// Verify that only DISCONNECTED -> DISCONNECTED broadcast is fired
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS))
.sendBroadcastMultiplePermissions(MockitoHamcrest.argThat(
AllOf.allOf(IntentMatchers.hasAction(
BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED),
IntentMatchers.hasExtra(BluetoothProfile.EXTRA_STATE,
BluetoothProfile.STATE_DISCONNECTED),
IntentMatchers.hasExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
BluetoothProfile.STATE_DISCONNECTED))),
any(String[].class),
any(BroadcastOptions.class));
// Check we are in disconnected state still.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
}
/**
* Test that an incoming connection with high priority is accepted
*/
@MediumTest
@Test
public void testIncomingPriorityAccept() {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
connStCh.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS))
.sendBroadcastMultiplePermissions(intentArgument1.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
// Send a message to trigger SLC connection
StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
setUpAndroidAt(false);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(2))
.sendBroadcastMultiplePermissions(intentArgument2.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
}
/**
* Test that an incoming connection that times out
*/
@MediumTest
@Test
public void testIncomingTimeout() {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
connStCh.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS))
.sendBroadcastMultiplePermissions(intentArgument1.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService,
timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(2))
.sendBroadcastMultiplePermissions(intentArgument2.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
}
/**
* Test that In Band Ringtone information is relayed from phone.
*/
@LargeTest
@Test
@FlakyTest
public void testInBandRingtone() {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
// Inject an event for when incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
connStCh.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
int expectedBroadcastIndex = 1;
int expectedBroadcastMultiplePermissionsIndex = 1;
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService,
timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastMultiplePermissionsIndex++))
.sendBroadcastMultiplePermissions(intentArgument.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Send a message to trigger SLC connection
StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
setUpAndroidAt(false);
verify(mHeadsetClientService,
timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastMultiplePermissionsIndex++))
.sendBroadcastMultiplePermissions(intentArgument.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
event.valueInt = 0;
event.device = mTestDevice;
// Enable In Band Ring and verify state gets propagated.
StackEvent eventInBandRing = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
eventInBandRing.valueInt = 1;
eventInBandRing.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex++))
.sendBroadcast(
intentArgument.capture(),
anyString(), any(Bundle.class));
Assert.assertEquals(1,
intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
-1));
Assert.assertEquals(true, mHeadsetClientStateMachine.getInBandRing());
// Simulate a new incoming phone call
StackEvent eventCallStatusUpdated = new StackEvent(StackEvent.EVENT_TYPE_CLIP);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCallStatusUpdated);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mHeadsetClientService,
timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex - 1))
.sendBroadcast(
intentArgument.capture(),
anyString(),any(Bundle.class));
// Provide information about the new call
StackEvent eventIncomingCall = new StackEvent(StackEvent.EVENT_TYPE_CURRENT_CALLS);
eventIncomingCall.valueInt = 1; //index
eventIncomingCall.valueInt2 = 1; //direction
eventIncomingCall.valueInt3 = 4; //state
eventIncomingCall.valueInt4 = 0; //multi party
eventIncomingCall.valueString = "5551212"; //phone number
eventIncomingCall.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventIncomingCall);
verify(mHeadsetClientService,
timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex - 1))
.sendBroadcast(
intentArgument.capture(),
anyString(), any(Bundle.class));
// Signal that the complete list of calls was received.
StackEvent eventCommandStatus = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
eventCommandStatus.valueInt = AT_OK;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventCommandStatus);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mHeadsetClientService,
timeout(QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS).times(expectedBroadcastIndex++))
.sendBroadcast(
intentArgument.capture(),
anyString(), any(Bundle.class));
// Verify that the new call is being registered with the inBandRing flag set.
Assert.assertEquals(true,
((HfpClientCall) intentArgument.getValue().getParcelableExtra(
BluetoothHeadsetClient.EXTRA_CALL)).isInBandRing());
// Disable In Band Ring and verify state gets propagated.
eventInBandRing.valueInt = 0;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, eventInBandRing);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex++))
.sendBroadcast(
intentArgument.capture(),
anyString(), any(Bundle.class));
Assert.assertEquals(0,
intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_IN_BAND_RING,
-1));
Assert.assertEquals(false, mHeadsetClientStateMachine.getInBandRing());
}
/* Utility function to simulate HfpClient is connected. */
private int setUpHfpClientConnection(int startBroadcastIndex) {
// Trigger an incoming connection is requested
StackEvent connStCh = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
connStCh.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, connStCh);
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
.sendBroadcastMultiplePermissions(intentArgument.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
startBroadcastIndex++;
return startBroadcastIndex;
}
/* Utility function to simulate SLC connection. */
private int setUpServiceLevelConnection(int startBroadcastIndex) {
return setUpServiceLevelConnection(startBroadcastIndex, false);
}
private int setUpServiceLevelConnection(int startBroadcastIndex, boolean androidAtSupported) {
// Trigger SLC connection
StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
setUpAndroidAt(androidAtSupported);
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
.sendBroadcastMultiplePermissions(intentArgument.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
startBroadcastIndex++;
return startBroadcastIndex;
}
/**
* Set up and verify AT Android related commands and events.
* Make sure this method is invoked after SLC is setup.
*/
private void setUpAndroidAt(boolean androidAtSupported) {
verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=?");
if (androidAtSupported) {
StackEvent unknownEvt = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
unknownEvt.valueString = "+ANDROID: 1";
unknownEvt.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, unknownEvt);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mHeadsetClientService).setAudioPolicyRemoteSupported(mTestDevice, true);
mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true);
} else {
// receive CMD_RESULT CME_ERROR due to remote not supporting Android AT
StackEvent cmdResEvt = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
cmdResEvt.valueInt = StackEvent.CMD_RESULT_TYPE_CME_ERROR;
cmdResEvt.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, cmdResEvt);
}
}
/* Utility function: supported AT command should lead to native call */
private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
Message msg = mHeadsetClientStateMachine.obtainMessage(
HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
mHeadsetClientStateMachine.sendMessage(msg);
verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1)).sendATCmd(
mTestDevice,
HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_VENDOR_SPECIFIC_CMD,
0, 0, atCommand);
}
/**
* Test: supported vendor specific command: set operation
*/
@LargeTest
@Test
public void testSupportedVendorAtCommandSet() {
int vendorId = BluetoothAssignedNumbers.APPLE;
String atCommand = "+XAPL=ABCD-1234-0100,100";
runSupportedVendorAtCommand(atCommand, vendorId);
}
/**
* Test: supported vendor specific command: read operation
*/
@LargeTest
@Test
public void testSupportedVendorAtCommandRead() {
int vendorId = BluetoothAssignedNumbers.APPLE;
String atCommand = "+APLSIRI?";
runSupportedVendorAtCommand(atCommand, vendorId);
}
/* utility function: unsupported vendor specific command shall be filtered. */
public void runUnsupportedVendorAtCommand(String atCommand, int vendorId) {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
Message msg = mHeadsetClientStateMachine.obtainMessage(
HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND, vendorId, 0, atCommand);
mHeadsetClientStateMachine.sendMessage(msg);
verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(0))
.sendATCmd(any(), anyInt(), anyInt(), anyInt(), any());
}
/**
* Test: unsupported vendor specific command shall be filtered: bad command code
*/
@LargeTest
@Test
public void testUnsupportedVendorAtCommandBadCode() {
String atCommand = "+XAAPL=ABCD-1234-0100,100";
int vendorId = BluetoothAssignedNumbers.APPLE;
runUnsupportedVendorAtCommand(atCommand, vendorId);
}
/**
* Test: unsupported vendor specific command shall be filtered:
* no back to back command
*/
@LargeTest
@Test
public void testUnsupportedVendorAtCommandBackToBack() {
String atCommand = "+XAPL=ABCD-1234-0100,100; +XAPL=ab";
int vendorId = BluetoothAssignedNumbers.APPLE;
runUnsupportedVendorAtCommand(atCommand, vendorId);
}
/* Utility test function: supported vendor specific event
* shall lead to broadcast intent
*/
private void runSupportedVendorEvent(int vendorId, String vendorEventCode,
String vendorEventArgument) {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
int expectedBroadcastMultiplePermissionsIndex = 1;
expectedBroadcastMultiplePermissionsIndex =
setUpHfpClientConnection(expectedBroadcastMultiplePermissionsIndex);
expectedBroadcastMultiplePermissionsIndex =
setUpServiceLevelConnection(expectedBroadcastMultiplePermissionsIndex);
// Simulate a known event arrive
String vendorEvent = vendorEventCode + vendorEventArgument;
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
event.device = mTestDevice;
event.valueString = vendorEvent;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
// Validate broadcast intent
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex))
.sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
Assert.assertEquals(BluetoothHeadsetClient.ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT,
intentArgument.getValue().getAction());
Assert.assertEquals(vendorId,
intentArgument.getValue().getIntExtra(BluetoothHeadsetClient.EXTRA_VENDOR_ID, -1));
Assert.assertEquals(vendorEventCode,
intentArgument.getValue().getStringExtra(
BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_CODE));
Assert.assertEquals(vendorEvent,
intentArgument.getValue().getStringExtra(
BluetoothHeadsetClient.EXTRA_VENDOR_EVENT_FULL_ARGS));
}
/**
* Test: supported vendor specific response: response to read command
*/
@LargeTest
@Test
public void testSupportedVendorEventReadResponse() {
final int vendorId = BluetoothAssignedNumbers.APPLE;
final String vendorResponseCode = "+XAPL=";
final String vendorResponseArgument = "iPhone,2";
runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
}
/**
* Test: supported vendor specific response: response to test command
*/
@LargeTest
@Test
public void testSupportedVendorEventTestResponse() {
final int vendorId = BluetoothAssignedNumbers.APPLE;
final String vendorResponseCode = "+APLSIRI:";
final String vendorResponseArgumentWithSpace = " 2";
runSupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgumentWithSpace);
}
/* Utility test function: unsupported vendor specific response shall be filtered out*/
public void runUnsupportedVendorEvent(int vendorId, String vendorEventCode,
String vendorEventArgument) {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
// Simulate an unknown event arrive
String vendorEvent = vendorEventCode + vendorEventArgument;
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
event.device = mTestDevice;
event.valueString = vendorEvent;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
// Validate no broadcast intent
verify(mHeadsetClientService, atMost(expectedBroadcastIndex - 1))
.sendBroadcast(any(), anyString(), any(Bundle.class));
}
/**
* Test unsupported vendor response: bad read response
*/
@LargeTest
@Test
public void testUnsupportedVendorEventBadReadResponse() {
final int vendorId = BluetoothAssignedNumbers.APPLE;
final String vendorResponseCode = "+XAAPL=";
final String vendorResponseArgument = "iPhone,2";
runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
}
/**
* Test unsupported vendor response: bad test response
*/
@LargeTest
@Test
public void testUnsupportedVendorEventBadTestResponse() {
final int vendorId = BluetoothAssignedNumbers.APPLE;
final String vendorResponseCode = "+AAPLSIRI:";
final String vendorResponseArgument = "2";
runUnsupportedVendorEvent(vendorId, vendorResponseCode, vendorResponseArgument);
}
/**
* Test voice recognition state change broadcast.
*/
@MediumTest
@Test
public void testVoiceRecognitionStateChange() {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
doReturn(true).when(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).stopVoiceRecognition(any(BluetoothDevice.class));
int expectedBroadcastIndex = 1;
int expectedBroadcastMultiplePermissionsIndex = 1;
expectedBroadcastMultiplePermissionsIndex =
setUpHfpClientConnection(expectedBroadcastMultiplePermissionsIndex);
expectedBroadcastMultiplePermissionsIndex =
setUpServiceLevelConnection(expectedBroadcastMultiplePermissionsIndex);
// Simulate a voice recognition start
mHeadsetClientStateMachine.sendMessage(VOICE_RECOGNITION_START);
// Signal that the complete list of actions was received.
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
event.device = mTestDevice;
event.valueInt = AT_OK;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
expectedBroadcastIndex = verifyVoiceRecognitionBroadcast(expectedBroadcastIndex,
HeadsetClientHalConstants.VR_STATE_STARTED);
// Simulate a voice recognition stop
mHeadsetClientStateMachine.sendMessage(VOICE_RECOGNITION_STOP);
// Signal that the complete list of actions was received.
event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
event.device = mTestDevice;
event.valueInt = AT_OK;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, event);
verifyVoiceRecognitionBroadcast(expectedBroadcastIndex,
HeadsetClientHalConstants.VR_STATE_STOPPED);
}
private int verifyVoiceRecognitionBroadcast(int expectedBroadcastIndex, int expectedState) {
// Validate broadcast intent
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastIndex))
.sendBroadcast(intentArgument.capture(), anyString(), any(Bundle.class));
Assert.assertEquals(BluetoothHeadsetClient.ACTION_AG_EVENT,
intentArgument.getValue().getAction());
int state = intentArgument.getValue().getIntExtra(
BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, -1);
Assert.assertEquals(expectedState, state);
return expectedBroadcastIndex + 1;
}
/**
* Test send BIEV command
*/
@MediumTest
@Test
public void testSendBIEVCommand() {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
int indicator_id = 2;
int indicator_value = 50;
Message msg = mHeadsetClientStateMachine.obtainMessage(HeadsetClientStateMachine.SEND_BIEV);
msg.arg1 = indicator_id;
msg.arg2 = indicator_value;
mHeadsetClientStateMachine.sendMessage(msg);
verify(mNativeInterface, timeout(STANDARD_WAIT_MILLIS).times(1))
.sendATCmd(
mTestDevice,
HeadsetClientHalConstants.HANDSFREECLIENT_AT_CMD_BIEV,
indicator_id,
indicator_value,
null);
}
/**
* Test state machine shall try to send AT+BIEV command to AG
* to update an init battery level.
*/
@MediumTest
@Test
public void testSendBatteryUpdateIndicatorWhenConnect() {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(1))
.updateBatteryLevel();
}
@Test
public void testBroadcastAudioState() {
mHeadsetClientStateMachine.broadcastAudioState(mTestDevice,
BluetoothHeadsetClient.STATE_AUDIO_CONNECTED,
BluetoothHeadsetClient.STATE_AUDIO_CONNECTING);
verify(mHeadsetClientService).sendBroadcast(any(), any(), any());
}
@Test
public void testCallsInState() {
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING,
"1", false, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
Assert.assertEquals(
mHeadsetClientStateMachine.callsInState(HfpClientCall.CALL_STATE_WAITING), 1);
}
@Test
public void testEnterPrivateMode() {
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
doReturn(true).when(mNativeInterface).handleCallAction(null,
HeadsetClientHalConstants.CALL_ACTION_CHLD_2X, 0);
mHeadsetClientStateMachine.enterPrivateMode(0);
Pair expectedPair = new Pair<Integer, Object>(ENTER_PRIVATE_MODE, call);
Assert.assertEquals(mHeadsetClientStateMachine.mQueuedActions.peek(), expectedPair);
}
@Test
public void testExplicitCallTransfer() {
HfpClientCall callOne = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
"1", true, false, false);
HfpClientCall callTwo = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, callOne);
mHeadsetClientStateMachine.mCalls.put(1, callTwo);
doReturn(true).when(mNativeInterface).handleCallAction(null,
HeadsetClientHalConstants.CALL_ACTION_CHLD_4, -1);
mHeadsetClientStateMachine.explicitCallTransfer();
Pair expectedPair = new Pair<Integer, Object>(EXPLICIT_CALL_TRANSFER, 0);
Assert.assertEquals(mHeadsetClientStateMachine.mQueuedActions.peek(), expectedPair);
}
@Test
public void testSetAudioRouteAllowed() {
mHeadsetClientStateMachine.setAudioRouteAllowed(true);
Assert.assertTrue(mHeadsetClientStateMachine.getAudioRouteAllowed());
}
@Test
public void testGetAudioState_withCurrentDeviceNull() {
Assert.assertNull(mHeadsetClientStateMachine.mCurrentDevice);
Assert.assertEquals(mHeadsetClientStateMachine.getAudioState(mTestDevice),
BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED);
}
@Test
public void testGetAudioState_withCurrentDeviceNotNull() {
int audioState = 1;
mHeadsetClientStateMachine.mAudioState = audioState;
mHeadsetClientStateMachine.mCurrentDevice = mTestDevice;
Assert.assertEquals(mHeadsetClientStateMachine.getAudioState(mTestDevice), audioState);
}
@Test
public void testGetCall_withMatchingState() {
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
int[] states = new int[1];
states[0] = HfpClientCall.CALL_STATE_ACTIVE;
Assert.assertEquals(mHeadsetClientStateMachine.getCall(states), call);
}
@Test
public void testGetCall_withNoMatchingState() {
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
int[] states = new int[1];
states[0] = HfpClientCall.CALL_STATE_ACTIVE;
Assert.assertNull(mHeadsetClientStateMachine.getCall(states));
}
@Test
public void testGetConnectionState_withNullDevice() {
Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(null),
BluetoothProfile.STATE_DISCONNECTED);
}
@Test
public void testGetConnectionState_withNonNullDevice() {
mHeadsetClientStateMachine.mCurrentDevice = mTestDevice;
Assert.assertEquals(mHeadsetClientStateMachine.getConnectionState(mTestDevice),
BluetoothProfile.STATE_DISCONNECTED);
}
@Test
public void testGetConnectionStateFromAudioState() {
Assert.assertEquals(HeadsetClientStateMachine.getConnectionStateFromAudioState(
BluetoothHeadsetClient.STATE_AUDIO_CONNECTED), BluetoothAdapter.STATE_CONNECTED);
Assert.assertEquals(HeadsetClientStateMachine.getConnectionStateFromAudioState(
BluetoothHeadsetClient.STATE_AUDIO_CONNECTING), BluetoothAdapter.STATE_CONNECTING);
Assert.assertEquals(HeadsetClientStateMachine.getConnectionStateFromAudioState(
BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED),
BluetoothAdapter.STATE_DISCONNECTED);
int invalidAudioState = 3;
Assert.assertEquals(
HeadsetClientStateMachine.getConnectionStateFromAudioState(invalidAudioState),
BluetoothAdapter.STATE_DISCONNECTED);
}
@Test
public void testGetCurrentAgEvents() {
Bundle bundle = mHeadsetClientStateMachine.getCurrentAgEvents();
Assert.assertEquals(bundle.getString(BluetoothHeadsetClient.EXTRA_SUBSCRIBER_INFO),
mHeadsetClientStateMachine.mSubscriberInfo);
}
@Test
public void testGetCurrentAgFeatures() {
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_3WAY;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC;
Set<Integer> features = mHeadsetClientStateMachine.getCurrentAgFeatures();
Assert.assertTrue(features.contains(HeadsetClientHalConstants.PEER_FEAT_3WAY));
Assert.assertTrue(features.contains(HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_VREC;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL;
features = mHeadsetClientStateMachine.getCurrentAgFeatures();
Assert.assertTrue(features.contains(HeadsetClientHalConstants.PEER_FEAT_VREC));
Assert.assertTrue(features.contains(HeadsetClientHalConstants.CHLD_FEAT_REL));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_REJECT;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL_ACC;
features = mHeadsetClientStateMachine.getCurrentAgFeatures();
Assert.assertTrue(features.contains(HeadsetClientHalConstants.PEER_FEAT_REJECT));
Assert.assertTrue(features.contains(HeadsetClientHalConstants.CHLD_FEAT_REL_ACC));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_ECC;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE;
features = mHeadsetClientStateMachine.getCurrentAgFeatures();
Assert.assertTrue(features.contains(HeadsetClientHalConstants.PEER_FEAT_ECC));
Assert.assertTrue(features.contains(HeadsetClientHalConstants.CHLD_FEAT_MERGE));
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH;
features = mHeadsetClientStateMachine.getCurrentAgFeatures();
Assert.assertTrue(features.contains(HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH));
}
@Test
public void testGetCurrentAgFeaturesBundle() {
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_3WAY;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_HOLD_ACC;
Bundle bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
Assert.assertTrue(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_3WAY_CALLING));
Assert.assertTrue(bundle.getBoolean(
BluetoothHeadsetClient.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_VREC;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL;
bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
Assert.assertTrue(
bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_VOICE_RECOGNITION));
Assert.assertTrue(bundle.getBoolean(
BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_REJECT;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_REL_ACC;
bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
Assert.assertTrue(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_REJECT_CALL));
Assert.assertTrue(
bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT));
mHeadsetClientStateMachine.mPeerFeatures = HeadsetClientHalConstants.PEER_FEAT_ECC;
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE;
bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
Assert.assertTrue(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_ECC));
Assert.assertTrue(bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE));
mHeadsetClientStateMachine.mChldFeatures = HeadsetClientHalConstants.CHLD_FEAT_MERGE_DETACH;
bundle = mHeadsetClientStateMachine.getCurrentAgFeaturesBundle();
Assert.assertTrue(
bundle.getBoolean(BluetoothHeadsetClient.EXTRA_AG_FEATURE_MERGE_AND_DETACH));
}
@Test
public void testGetCurrentCalls() {
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_WAITING,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
List<HfpClientCall> currentCalls = mHeadsetClientStateMachine.getCurrentCalls();
Assert.assertEquals(currentCalls.get(0), call);
}
@Test
public void testGetMessageName() {
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(StackEvent.STACK_EVENT),
"STACK_EVENT");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.CONNECT),
"CONNECT");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.DISCONNECT),
"DISCONNECT");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.CONNECT_AUDIO),
"CONNECT_AUDIO");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.DISCONNECT_AUDIO), "DISCONNECT_AUDIO");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(VOICE_RECOGNITION_START),
"VOICE_RECOGNITION_START");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(VOICE_RECOGNITION_STOP),
"VOICE_RECOGNITION_STOP");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.SET_MIC_VOLUME),
"SET_MIC_VOLUME");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.SET_SPEAKER_VOLUME), "SET_SPEAKER_VOLUME");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.DIAL_NUMBER),
"DIAL_NUMBER");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.ACCEPT_CALL),
"ACCEPT_CALL");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.REJECT_CALL),
"REJECT_CALL");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.HOLD_CALL),
"HOLD_CALL");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.TERMINATE_CALL),
"TERMINATE_CALL");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(ENTER_PRIVATE_MODE),
"ENTER_PRIVATE_MODE");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.SEND_DTMF),
"SEND_DTMF");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(EXPLICIT_CALL_TRANSFER),
"EXPLICIT_CALL_TRANSFER");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.DISABLE_NREC),
"DISABLE_NREC");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.SEND_VENDOR_AT_COMMAND), "SEND_VENDOR_AT_COMMAND");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.SEND_BIEV),
"SEND_BIEV");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.QUERY_CURRENT_CALLS), "QUERY_CURRENT_CALLS");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.QUERY_OPERATOR_NAME), "QUERY_OPERATOR_NAME");
Assert.assertEquals(
HeadsetClientStateMachine.getMessageName(HeadsetClientStateMachine.SUBSCRIBER_INFO),
"SUBSCRIBER_INFO");
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(
HeadsetClientStateMachine.CONNECTING_TIMEOUT), "CONNECTING_TIMEOUT");
int unknownMessageInt = 54;
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(unknownMessageInt),
"UNKNOWN(" + unknownMessageInt + ")");
}
/**
* Tests and verify behavior of the case where remote device doesn't support
* At Android but tries to send audio policy.
*/
@Test
public void testAndroidAtRemoteNotSupported_StateTransition_setAudioPolicy() {
// Setup connection state machine to be in connected state
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
verify(mNativeInterface, never()).sendAndroidAt(mTestDevice, "+ANDROID:1,0,0,0");
}
@SmallTest
@Test
public void testSetGetCallAudioPolicy() {
// Return true for priority.
when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
int expectedBroadcastIndex = 1;
expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex, true);
BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder()
.setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
.setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
.setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
.build();
mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=1,1,2,1");
}
@Test
public void testDumpDoesNotCrash() {
mHeadsetClientStateMachine.dump(new StringBuilder());
}
@Test
public void testProcessDisconnectMessage_onDisconnectedState() {
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertEquals(STATE_DISCONNECTED,
mHeadsetClientStateMachine.getConnectionState(mTestDevice));
}
@Test
public void testProcessConnectMessage_onDisconnectedState() {
doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
sendMessageAndVerifyTransition(
mHeadsetClientStateMachine
.obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
HeadsetClientStateMachine.Connecting.class);
}
@Test
public void testStackEvent_toConnectingState_onDisconnectedState() {
allowConnection(true);
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
event.device = mTestDevice;
sendMessageAndVerifyTransition(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
HeadsetClientStateMachine.Connecting.class);
}
@Test
public void testStackEvent_toConnectingState_disallowConnection_onDisconnectedState() {
allowConnection(false);
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
event.device = mTestDevice;
sendMessageAndVerifyTransition(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
HeadsetClientStateMachine.Disconnected.class);
}
@Test
public void testProcessConnectMessage_onConnectingState() {
initToConnectingState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
HeadsetClientStateMachine.CONNECT));
}
@Test
public void testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectingState() {
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
event.device = mTestDevice;
sendMessageAndVerifyTransition(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
HeadsetClientStateMachine.Disconnected.class);
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
}
@Test
public void testProcessStackEvent_ConnectionStateChanged_Connected_onConnectingState() {
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
}
@Test
public void testProcessStackEvent_ConnectionStateChanged_Connecting_onConnectingState() {
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
}
@Test
public void testProcessStackEvent_Call_onConnectingState() {
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL);
event.valueInt = StackEvent.EVENT_TYPE_CALL;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
StackEvent.STACK_EVENT));
}
@Test
public void testProcessStackEvent_CmdResultWithEmptyQueuedActions_onConnectingState() {
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
event.valueInt = StackEvent.CMD_RESULT_TYPE_OK;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
}
@Test
public void testProcessStackEvent_Unknown_onConnectingState() {
String atCommand = "+ANDROID: 1";
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
event.valueString = atCommand;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
}
@Test
public void testProcessConnectTimeoutMessage_onConnectingState() {
initToConnectingState();
Message msg = mHeadsetClientStateMachine
.obtainMessage(HeadsetClientStateMachine.CONNECTING_TIMEOUT);
sendMessageAndVerifyTransition(msg, HeadsetClientStateMachine.Disconnected.class);
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
}
@Test
public void testProcessConnectMessage_onConnectedState() {
initToConnectedState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
}
@Test
public void testProcessDisconnectMessage_onConnectedState() {
initToConnectedState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).disconnect(any(BluetoothDevice.class));
}
@Test
public void testProcessConnectAudioMessage_onConnectedState() {
initToConnectedState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).connectAudio(any(BluetoothDevice.class));
}
@Test
public void testProcessDisconnectAudioMessage_onConnectedState() {
initToConnectedState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
}
@Test
public void testProcessVoiceRecognitionStartMessage_onConnectedState() {
initToConnectedState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
}
@Test
public void testProcessDisconnectMessage_onAudioOnState() {
initToAudioOnState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
HeadsetClientStateMachine.DISCONNECT));
}
@Test
public void testProcessDisconnectAudioMessage_onAudioOnState() {
initToAudioOnState();
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO,
mTestDevice);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
}
@Test
public void testProcessHoldCall_onAudioOnState() {
initToAudioOnState();
HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
"1", true, false, false);
mHeadsetClientStateMachine.mCalls.put(0, call);
int[] states = new int[1];
states[0] = HfpClientCall.CALL_STATE_ACTIVE;
mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.HOLD_CALL,
mTestDevice);
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
verify(mNativeInterface).handleCallAction(any(BluetoothDevice.class), anyInt(), eq(0));
}
@Test
public void testProcessStackEvent_ConnectionStateChanged_onAudioOnState() {
initToAudioOnState();
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
}
@Test
public void testProcessStackEvent_AudioStateChanged_onAudioOnState() {
initToAudioOnState();
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
}
/**
* Allow/disallow connection to any device
*
* @param allow if true, connection is allowed
*/
private void allowConnection(boolean allow) {
mHeadsetClientStateMachine.allowConnect = allow;
}
private void initToConnectingState() {
doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
sendMessageAndVerifyTransition(
mHeadsetClientStateMachine
.obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
HeadsetClientStateMachine.Connecting.class);
}
private void initToConnectedState() {
String atCommand = "+ANDROID: 1";
initToConnectingState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
event.valueString = atCommand;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
}
private void initToAudioOnState() {
mHeadsetClientStateMachine.setAudioRouteAllowed(true);
initToConnectedState();
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED;
event.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(
mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
}
private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
Mockito.clearInvocations(mHeadsetClientService);
mHeadsetClientStateMachine.sendMessage(msg);
// Verify that one connection state broadcast is executed
verify(mHeadsetClientService, timeout(TIMEOUT_MS)).sendBroadcastMultiplePermissions(
any(Intent.class), any(String[].class), any(BroadcastOptions.class)
);
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(type));
}
public static class TestHeadsetClientStateMachine extends HeadsetClientStateMachine {
Boolean allowConnect = null;
TestHeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
Looper looper, NativeInterface nativeInterface) {
super(context, headsetService, looper, nativeInterface);
}
public boolean doesSuperHaveDeferredMessages(int what) {
return super.hasDeferredMessages(what);
}
@Override
public boolean okToConnect(BluetoothDevice device) {
return allowConnect != null ? allowConnect : super.okToConnect(device);
}
}
}