Loading android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +25 −3 Original line number Diff line number Diff line Loading @@ -114,6 +114,10 @@ public class HeadsetStateMachine extends StateMachine { // NOTE: the value is not "final" - it is modified in the unit tests @VisibleForTesting static int sConnectTimeoutMs = 30000; // Number of times we should retry disconnecting audio before // disconnecting the device. private static final int MAX_RETRY_DISCONNECT_AUDIO = 3; private static final HeadsetAgIndicatorEnableState DEFAULT_AG_INDICATOR_ENABLE_STATE = new HeadsetAgIndicatorEnableState(true, true, true, true); Loading Loading @@ -149,6 +153,8 @@ public class HeadsetStateMachine extends StateMachine { private final AtPhonebook mPhonebook; // HSP specific private boolean mNeedDialingOutReply; // Audio disconnect timeout retry count private int mAudioDisconnectRetry = 0; // Keys are AT commands, and values are the company IDs. private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID; Loading Loading @@ -1042,6 +1048,10 @@ public class HeadsetStateMachine extends StateMachine { // state. This is to prevent auto connect attempts from disconnecting // devices that previously successfully connected. removeDeferredMessages(CONNECT); } else if (mPrevState == mAudioDisconnecting) { // Reset audio disconnecting retry count. Either the disconnection was successful // or the retry count reached MAX_RETRY_DISCONNECT_AUDIO. mAudioDisconnectRetry = 0; } broadcastStateTransitions(); } Loading Loading @@ -1283,7 +1293,7 @@ public class HeadsetStateMachine extends StateMachine { stateLogW("CONNECT_AUDIO device is not connected " + device); break; } stateLogW("CONNECT_AUDIO device auido is already connected " + device); stateLogW("CONNECT_AUDIO device audio is already connected " + device); break; } case DISCONNECT_AUDIO: { Loading Loading @@ -1385,8 +1395,18 @@ public class HeadsetStateMachine extends StateMachine { stateLogW("CONNECT_TIMEOUT for unknown device " + device); break; } stateLogW("CONNECT_TIMEOUT"); if (mAudioDisconnectRetry == MAX_RETRY_DISCONNECT_AUDIO) { stateLogW("CONNECT_TIMEOUT: Disconnecting device"); // Restoring state to Connected with message DISCONNECT deferMessage(obtainMessage(DISCONNECT, mDevice)); transitionTo(mConnected); } else { mAudioDisconnectRetry += 1; stateLogW("CONNECT_TIMEOUT: retrying " + (MAX_RETRY_DISCONNECT_AUDIO - mAudioDisconnectRetry) + " more time(s)"); transitionTo(mAudioOn); } break; } default: Loading @@ -1407,6 +1427,8 @@ public class HeadsetStateMachine extends StateMachine { break; case HeadsetHalConstants.AUDIO_STATE_CONNECTED: stateLogW("processAudioEvent: audio disconnection failed"); // Audio connected, resetting disconnect retry. mAudioDisconnectRetry = 0; transitionTo(mAudioOn); break; case HeadsetHalConstants.AUDIO_STATE_CONNECTING: Loading android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java +41 −12 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.hfp; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; Loading Loading @@ -69,6 +70,7 @@ public class HeadsetStateMachineTest { private static final int CONNECT_TIMEOUT_TEST_WAIT_MILLIS = CONNECT_TIMEOUT_TEST_MILLIS * 3 / 2; private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private static final String TEST_PHONE_NUMBER = "1234567890"; private static final int MAX_RETRY_DISCONNECT_AUDIO = 3; private Context mTargetContext; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; Loading Loading @@ -742,23 +744,50 @@ public class HeadsetStateMachineTest { } /** * Test state transition from AudioDisconnecting to Connected state via * CONNECT_TIMEOUT message * Test state transition from AudioDisconnecting to AudioOn state via CONNECT_TIMEOUT message * until retry count is reached, then test transition to Disconnecting state. */ @Test public void testStateTransition_AudioDisconnectingToConnected_Timeout() { public void testStateTransition_AudioDisconnectingToAudioOnAndDisconnecting_Timeout() { int numBroadcastsSent = setUpAudioDisconnectingState(); // Wait for connection to timeout numBroadcastsSent++; for (int i = 0; i <= MAX_RETRY_DISCONNECT_AUDIO; i++) { if (i > 0) { // Skip first AUDIO_DISCONNECTING init as it was setup before the loop mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mTestDevice); // No new broadcast due to lack of AUDIO_DISCONNECTING intent variable verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS) .times(numBroadcastsSent)).sendBroadcastAsUser( any(Intent.class), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); if (i == MAX_RETRY_DISCONNECT_AUDIO) { // Increment twice numBroadcastsSent as DISCONNECT message is added on max retry numBroadcastsSent += 2; } else { numBroadcastsSent++; } } verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times( numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); if (i < MAX_RETRY_DISCONNECT_AUDIO) { // Test if state is AudioOn before max retry HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); } else { // Max retry count reached, test Disconnecting state HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice, BluetoothHeadset.STATE_DISCONNECTING, BluetoothHeadset.STATE_CONNECTED, mIntentArgument.getValue()); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); } } } /** Loading Loading
android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +25 −3 Original line number Diff line number Diff line Loading @@ -114,6 +114,10 @@ public class HeadsetStateMachine extends StateMachine { // NOTE: the value is not "final" - it is modified in the unit tests @VisibleForTesting static int sConnectTimeoutMs = 30000; // Number of times we should retry disconnecting audio before // disconnecting the device. private static final int MAX_RETRY_DISCONNECT_AUDIO = 3; private static final HeadsetAgIndicatorEnableState DEFAULT_AG_INDICATOR_ENABLE_STATE = new HeadsetAgIndicatorEnableState(true, true, true, true); Loading Loading @@ -149,6 +153,8 @@ public class HeadsetStateMachine extends StateMachine { private final AtPhonebook mPhonebook; // HSP specific private boolean mNeedDialingOutReply; // Audio disconnect timeout retry count private int mAudioDisconnectRetry = 0; // Keys are AT commands, and values are the company IDs. private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID; Loading Loading @@ -1042,6 +1048,10 @@ public class HeadsetStateMachine extends StateMachine { // state. This is to prevent auto connect attempts from disconnecting // devices that previously successfully connected. removeDeferredMessages(CONNECT); } else if (mPrevState == mAudioDisconnecting) { // Reset audio disconnecting retry count. Either the disconnection was successful // or the retry count reached MAX_RETRY_DISCONNECT_AUDIO. mAudioDisconnectRetry = 0; } broadcastStateTransitions(); } Loading Loading @@ -1283,7 +1293,7 @@ public class HeadsetStateMachine extends StateMachine { stateLogW("CONNECT_AUDIO device is not connected " + device); break; } stateLogW("CONNECT_AUDIO device auido is already connected " + device); stateLogW("CONNECT_AUDIO device audio is already connected " + device); break; } case DISCONNECT_AUDIO: { Loading Loading @@ -1385,8 +1395,18 @@ public class HeadsetStateMachine extends StateMachine { stateLogW("CONNECT_TIMEOUT for unknown device " + device); break; } stateLogW("CONNECT_TIMEOUT"); if (mAudioDisconnectRetry == MAX_RETRY_DISCONNECT_AUDIO) { stateLogW("CONNECT_TIMEOUT: Disconnecting device"); // Restoring state to Connected with message DISCONNECT deferMessage(obtainMessage(DISCONNECT, mDevice)); transitionTo(mConnected); } else { mAudioDisconnectRetry += 1; stateLogW("CONNECT_TIMEOUT: retrying " + (MAX_RETRY_DISCONNECT_AUDIO - mAudioDisconnectRetry) + " more time(s)"); transitionTo(mAudioOn); } break; } default: Loading @@ -1407,6 +1427,8 @@ public class HeadsetStateMachine extends StateMachine { break; case HeadsetHalConstants.AUDIO_STATE_CONNECTED: stateLogW("processAudioEvent: audio disconnection failed"); // Audio connected, resetting disconnect retry. mAudioDisconnectRetry = 0; transitionTo(mAudioOn); break; case HeadsetHalConstants.AUDIO_STATE_CONNECTING: Loading
android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java +41 −12 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.hfp; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static org.mockito.Mockito.*; import android.bluetooth.BluetoothAdapter; Loading Loading @@ -69,6 +70,7 @@ public class HeadsetStateMachineTest { private static final int CONNECT_TIMEOUT_TEST_WAIT_MILLIS = CONNECT_TIMEOUT_TEST_MILLIS * 3 / 2; private static final int ASYNC_CALL_TIMEOUT_MILLIS = 250; private static final String TEST_PHONE_NUMBER = "1234567890"; private static final int MAX_RETRY_DISCONNECT_AUDIO = 3; private Context mTargetContext; private BluetoothAdapter mAdapter; private HandlerThread mHandlerThread; Loading Loading @@ -742,23 +744,50 @@ public class HeadsetStateMachineTest { } /** * Test state transition from AudioDisconnecting to Connected state via * CONNECT_TIMEOUT message * Test state transition from AudioDisconnecting to AudioOn state via CONNECT_TIMEOUT message * until retry count is reached, then test transition to Disconnecting state. */ @Test public void testStateTransition_AudioDisconnectingToConnected_Timeout() { public void testStateTransition_AudioDisconnectingToAudioOnAndDisconnecting_Timeout() { int numBroadcastsSent = setUpAudioDisconnectingState(); // Wait for connection to timeout numBroadcastsSent++; for (int i = 0; i <= MAX_RETRY_DISCONNECT_AUDIO; i++) { if (i > 0) { // Skip first AUDIO_DISCONNECTING init as it was setup before the loop mHeadsetStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO, mTestDevice); // No new broadcast due to lack of AUDIO_DISCONNECTING intent variable verify(mHeadsetService, after(ASYNC_CALL_TIMEOUT_MILLIS) .times(numBroadcastsSent)).sendBroadcastAsUser( any(Intent.class), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.AudioDisconnecting.class)); if (i == MAX_RETRY_DISCONNECT_AUDIO) { // Increment twice numBroadcastsSent as DISCONNECT message is added on max retry numBroadcastsSent += 2; } else { numBroadcastsSent++; } } verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times( numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(), eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT), any(Bundle.class)); if (i < MAX_RETRY_DISCONNECT_AUDIO) { // Test if state is AudioOn before max retry HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED, mIntentArgument.getValue()); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class)); IsInstanceOf.instanceOf(HeadsetStateMachine.AudioOn.class)); } else { // Max retry count reached, test Disconnecting state HeadsetTestUtils.verifyConnectionStateBroadcast(mTestDevice, BluetoothHeadset.STATE_DISCONNECTING, BluetoothHeadset.STATE_CONNECTED, mIntentArgument.getValue()); Assert.assertThat(mHeadsetStateMachine.getCurrentState(), IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class)); } } } /** Loading