Loading android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +38 −19 Original line number Diff line number Diff line Loading @@ -110,10 +110,14 @@ public class HeadsetClientStateMachine extends StateMachine { public static final int SEND_BIEV = 22; // internal actions private static final int QUERY_CURRENT_CALLS = 50; private static final int QUERY_OPERATOR_NAME = 51; private static final int SUBSCRIBER_INFO = 52; private static final int CONNECTING_TIMEOUT = 53; @VisibleForTesting static final int QUERY_CURRENT_CALLS = 50; @VisibleForTesting static final int QUERY_OPERATOR_NAME = 51; @VisibleForTesting static final int SUBSCRIBER_INFO = 52; @VisibleForTesting static final int CONNECTING_TIMEOUT = 53; // special action to handle terminating specific call from multiparty call static final int TERMINATE_SPECIFIC_CALL = 53; Loading Loading @@ -144,7 +148,8 @@ public class HeadsetClientStateMachine extends StateMachine { // Set of calls that represent the accurate state of calls that exists on AG and the calls that // are currently in process of being notified to the AG from HF. private final Hashtable<Integer, HfpClientCall> mCalls = new Hashtable<>(); @VisibleForTesting final Hashtable<Integer, HfpClientCall> mCalls = new Hashtable<>(); // Set of calls received from AG via the AT+CLCC command. We use this map to update the mCalls // which is eventually used to inform the telephony stack of any changes to call on HF. private final Hashtable<Integer, HfpClientCall> mCallsUpdate = new Hashtable<>(); Loading @@ -156,19 +161,22 @@ public class HeadsetClientStateMachine extends StateMachine { private boolean mInBandRing; private String mOperatorName; private String mSubscriberInfo; @VisibleForTesting String mSubscriberInfo; private static int sMaxAmVcVol; private static int sMinAmVcVol; // queue of send actions (pair action, action_data) private Queue<Pair<Integer, Object>> mQueuedActions; @VisibleForTesting Queue<Pair<Integer, Object>> mQueuedActions; // last executed command, before action is complete e.g. waiting for some // indicator private Pair<Integer, Object> mPendingAction; private int mAudioState; @VisibleForTesting int mAudioState; // Indicates whether audio can be routed to the device private boolean mAudioRouteAllowed; private boolean mAudioWbs; Loading @@ -176,11 +184,14 @@ public class HeadsetClientStateMachine extends StateMachine { private final BluetoothAdapter mAdapter; // currently connected device private BluetoothDevice mCurrentDevice = null; @VisibleForTesting BluetoothDevice mCurrentDevice = null; // general peer features and call handling features private int mPeerFeatures; private int mChldFeatures; @VisibleForTesting int mPeerFeatures; @VisibleForTesting int mChldFeatures; // This is returned when requesting focus from AudioManager private AudioFocusRequest mAudioFocusRequest; Loading Loading @@ -257,7 +268,8 @@ public class HeadsetClientStateMachine extends StateMachine { return builder.toString(); } private static String getMessageName(int what) { @VisibleForTesting static String getMessageName(int what) { switch (what) { case StackEvent.STACK_EVENT: return "STACK_EVENT"; Loading Loading @@ -328,7 +340,8 @@ public class HeadsetClientStateMachine extends StateMachine { mQueuedActions.add(new Pair<Integer, Object>(action, data)); } private HfpClientCall getCall(int... states) { @VisibleForTesting HfpClientCall getCall(int... states) { logD("getFromCallsWithStates states:" + Arrays.toString(states)); for (HfpClientCall c : mCalls.values()) { for (int s : states) { Loading @@ -340,7 +353,8 @@ public class HeadsetClientStateMachine extends StateMachine { return null; } private int callsInState(int state) { @VisibleForTesting int callsInState(int state) { int i = 0; for (HfpClientCall c : mCalls.values()) { if (c.getState() == state) { Loading Loading @@ -708,7 +722,8 @@ public class HeadsetClientStateMachine extends StateMachine { } } private void enterPrivateMode(int idx) { @VisibleForTesting void enterPrivateMode(int idx) { logD("enterPrivateMode: " + idx); HfpClientCall c = mCalls.get(idx); Loading @@ -726,7 +741,8 @@ public class HeadsetClientStateMachine extends StateMachine { } } private void explicitCallTransfer() { @VisibleForTesting void explicitCallTransfer() { logD("explicitCallTransfer"); // can't transfer call if there is not enough call parties Loading Loading @@ -1879,7 +1895,8 @@ public class HeadsetClientStateMachine extends StateMachine { return BluetoothProfile.STATE_DISCONNECTED; } private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) { @VisibleForTesting void broadcastAudioState(BluetoothDevice device, int newState, int prevState) { BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED, AdapterService.getAdapterService().obfuscateAddress(device), getConnectionStateFromAudioState(newState), mAudioWbs Loading Loading @@ -2028,7 +2045,8 @@ public class HeadsetClientStateMachine extends StateMachine { return devices; } private byte[] getByteAddress(BluetoothDevice device) { @VisibleForTesting byte[] getByteAddress(BluetoothDevice device) { return Utils.getBytesFromAddress(device.getAddress()); } Loading @@ -2047,7 +2065,8 @@ public class HeadsetClientStateMachine extends StateMachine { return b; } private static int getConnectionStateFromAudioState(int audioState) { @VisibleForTesting static int getConnectionStateFromAudioState(int audioState) { switch (audioState) { case BluetoothHeadsetClient.STATE_AUDIO_CONNECTED: return BluetoothAdapter.STATE_CONNECTED; Loading android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +285 −0 Original line number Diff line number Diff line package com.android.bluetooth.hfpclient; 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; Loading @@ -19,6 +21,7 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.HandlerThread; import android.os.Message; import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.intent.matcher.IntentMatchers; Loading Loading @@ -46,6 +49,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.hamcrest.MockitoHamcrest; import java.util.List; import java.util.Set; @LargeTest @RunWith(AndroidJUnit4.class) public class HeadsetClientStateMachineTest { Loading Loading @@ -706,4 +712,283 @@ public class HeadsetClientStateMachineTest { 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 + ")"); } } Loading
android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java +38 −19 Original line number Diff line number Diff line Loading @@ -110,10 +110,14 @@ public class HeadsetClientStateMachine extends StateMachine { public static final int SEND_BIEV = 22; // internal actions private static final int QUERY_CURRENT_CALLS = 50; private static final int QUERY_OPERATOR_NAME = 51; private static final int SUBSCRIBER_INFO = 52; private static final int CONNECTING_TIMEOUT = 53; @VisibleForTesting static final int QUERY_CURRENT_CALLS = 50; @VisibleForTesting static final int QUERY_OPERATOR_NAME = 51; @VisibleForTesting static final int SUBSCRIBER_INFO = 52; @VisibleForTesting static final int CONNECTING_TIMEOUT = 53; // special action to handle terminating specific call from multiparty call static final int TERMINATE_SPECIFIC_CALL = 53; Loading Loading @@ -144,7 +148,8 @@ public class HeadsetClientStateMachine extends StateMachine { // Set of calls that represent the accurate state of calls that exists on AG and the calls that // are currently in process of being notified to the AG from HF. private final Hashtable<Integer, HfpClientCall> mCalls = new Hashtable<>(); @VisibleForTesting final Hashtable<Integer, HfpClientCall> mCalls = new Hashtable<>(); // Set of calls received from AG via the AT+CLCC command. We use this map to update the mCalls // which is eventually used to inform the telephony stack of any changes to call on HF. private final Hashtable<Integer, HfpClientCall> mCallsUpdate = new Hashtable<>(); Loading @@ -156,19 +161,22 @@ public class HeadsetClientStateMachine extends StateMachine { private boolean mInBandRing; private String mOperatorName; private String mSubscriberInfo; @VisibleForTesting String mSubscriberInfo; private static int sMaxAmVcVol; private static int sMinAmVcVol; // queue of send actions (pair action, action_data) private Queue<Pair<Integer, Object>> mQueuedActions; @VisibleForTesting Queue<Pair<Integer, Object>> mQueuedActions; // last executed command, before action is complete e.g. waiting for some // indicator private Pair<Integer, Object> mPendingAction; private int mAudioState; @VisibleForTesting int mAudioState; // Indicates whether audio can be routed to the device private boolean mAudioRouteAllowed; private boolean mAudioWbs; Loading @@ -176,11 +184,14 @@ public class HeadsetClientStateMachine extends StateMachine { private final BluetoothAdapter mAdapter; // currently connected device private BluetoothDevice mCurrentDevice = null; @VisibleForTesting BluetoothDevice mCurrentDevice = null; // general peer features and call handling features private int mPeerFeatures; private int mChldFeatures; @VisibleForTesting int mPeerFeatures; @VisibleForTesting int mChldFeatures; // This is returned when requesting focus from AudioManager private AudioFocusRequest mAudioFocusRequest; Loading Loading @@ -257,7 +268,8 @@ public class HeadsetClientStateMachine extends StateMachine { return builder.toString(); } private static String getMessageName(int what) { @VisibleForTesting static String getMessageName(int what) { switch (what) { case StackEvent.STACK_EVENT: return "STACK_EVENT"; Loading Loading @@ -328,7 +340,8 @@ public class HeadsetClientStateMachine extends StateMachine { mQueuedActions.add(new Pair<Integer, Object>(action, data)); } private HfpClientCall getCall(int... states) { @VisibleForTesting HfpClientCall getCall(int... states) { logD("getFromCallsWithStates states:" + Arrays.toString(states)); for (HfpClientCall c : mCalls.values()) { for (int s : states) { Loading @@ -340,7 +353,8 @@ public class HeadsetClientStateMachine extends StateMachine { return null; } private int callsInState(int state) { @VisibleForTesting int callsInState(int state) { int i = 0; for (HfpClientCall c : mCalls.values()) { if (c.getState() == state) { Loading Loading @@ -708,7 +722,8 @@ public class HeadsetClientStateMachine extends StateMachine { } } private void enterPrivateMode(int idx) { @VisibleForTesting void enterPrivateMode(int idx) { logD("enterPrivateMode: " + idx); HfpClientCall c = mCalls.get(idx); Loading @@ -726,7 +741,8 @@ public class HeadsetClientStateMachine extends StateMachine { } } private void explicitCallTransfer() { @VisibleForTesting void explicitCallTransfer() { logD("explicitCallTransfer"); // can't transfer call if there is not enough call parties Loading Loading @@ -1879,7 +1895,8 @@ public class HeadsetClientStateMachine extends StateMachine { return BluetoothProfile.STATE_DISCONNECTED; } private void broadcastAudioState(BluetoothDevice device, int newState, int prevState) { @VisibleForTesting void broadcastAudioState(BluetoothDevice device, int newState, int prevState) { BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_SCO_CONNECTION_STATE_CHANGED, AdapterService.getAdapterService().obfuscateAddress(device), getConnectionStateFromAudioState(newState), mAudioWbs Loading Loading @@ -2028,7 +2045,8 @@ public class HeadsetClientStateMachine extends StateMachine { return devices; } private byte[] getByteAddress(BluetoothDevice device) { @VisibleForTesting byte[] getByteAddress(BluetoothDevice device) { return Utils.getBytesFromAddress(device.getAddress()); } Loading @@ -2047,7 +2065,8 @@ public class HeadsetClientStateMachine extends StateMachine { return b; } private static int getConnectionStateFromAudioState(int audioState) { @VisibleForTesting static int getConnectionStateFromAudioState(int audioState) { switch (audioState) { case BluetoothHeadsetClient.STATE_AUDIO_CONNECTED: return BluetoothAdapter.STATE_CONNECTED; Loading
android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java +285 −0 Original line number Diff line number Diff line package com.android.bluetooth.hfpclient; 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; Loading @@ -19,6 +21,7 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.HandlerThread; import android.os.Message; import android.util.Pair; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.intent.matcher.IntentMatchers; Loading Loading @@ -46,6 +49,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.hamcrest.MockitoHamcrest; import java.util.List; import java.util.Set; @LargeTest @RunWith(AndroidJUnit4.class) public class HeadsetClientStateMachineTest { Loading Loading @@ -706,4 +712,283 @@ public class HeadsetClientStateMachineTest { 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 + ")"); } }