Merge "AbsoluteVolume change custom call to official API"
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
index db7f67e..fc54444 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkService.java
@@ -353,7 +353,15 @@
return true;
}
- void removeStateMachine(A2dpSinkStateMachine stateMachine) {
+ /**
+ * Remove a device's state machine.
+ *
+ * Called by the state machines when they disconnect.
+ *
+ * Visible for testing so it can be mocked and verified on.
+ */
+ @VisibleForTesting
+ public void removeStateMachine(A2dpSinkStateMachine stateMachine) {
mDeviceStateMap.remove(stateMachine.getDevice());
}
diff --git a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
index 85ebf2b..921753c 100644
--- a/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
+++ b/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachine.java
@@ -86,10 +86,6 @@
setInitialState(mDisconnected);
}
- protected String getConnectionStateChangedIntent() {
- return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED;
- }
-
/**
* Get the current connection state
*
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index fe3f98a..cddb9cc 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -114,6 +114,10 @@
// 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);
@@ -149,6 +153,8 @@
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;
@@ -1042,6 +1048,10 @@
// 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();
}
@@ -1283,7 +1293,7 @@
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: {
@@ -1385,8 +1395,18 @@
stateLogW("CONNECT_TIMEOUT for unknown device " + device);
break;
}
- stateLogW("CONNECT_TIMEOUT");
- transitionTo(mConnected);
+ 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:
@@ -1407,6 +1427,8 @@
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:
diff --git a/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java
new file mode 100644
index 0000000..74d3e3c
--- /dev/null
+++ b/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2021 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.a2dpsink;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAudioConfig;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.AudioFormat;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+import com.android.bluetooth.TestUtils;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class A2dpSinkStateMachineTest {
+ private Context mTargetContext;
+
+ private BluetoothAdapter mAdapter;
+ private BluetoothDevice mDevice;
+ private final String mDeviceAddress = "11:11:11:11:11:11";
+ @Mock private A2dpSinkService mService;
+ @Mock private A2dpSinkNativeInterface mNativeInterface;
+
+ A2dpSinkStateMachine mStateMachine;
+ private static final int TIMEOUT_MS = 1000;
+ private static final int CONNECT_TIMEOUT_MS = 6000;
+ private static final int UNHANDLED_MESSAGE = 9999;
+
+ @Before
+ public void setUp() throws Exception {
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+ Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
+ mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink));
+ MockitoAnnotations.initMocks(this);
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ assertThat(mAdapter).isNotNull();
+ mDevice = mAdapter.getRemoteDevice(mDeviceAddress);
+
+ doNothing().when(mService).removeStateMachine(any(A2dpSinkStateMachine.class));
+
+ mStateMachine = new A2dpSinkStateMachine(mDevice, mService, mNativeInterface);
+ mStateMachine.start();
+ assertThat(mStateMachine.getDevice()).isEqualTo(mDevice);
+ assertThat(mStateMachine.getAudioConfig()).isNull();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_a2dp_sink)) {
+ return;
+ }
+ mStateMachine = null;
+ mDevice = null;
+ mAdapter = null;
+ }
+
+ private void mockDeviceConnectionPolicy(BluetoothDevice device, int policy) {
+ doReturn(policy).when(mService).getConnectionPolicy(device);
+ }
+
+ private void sendConnectionEvent(int state) {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT,
+ StackEvent.connectionStateChanged(mDevice, state));
+ }
+
+ private void sendAudioConfigChangedEvent(int sampleRate, int channelCount) {
+ mStateMachine.sendMessage(A2dpSinkStateMachine.STACK_EVENT,
+ StackEvent.audioConfigChanged(mDevice, sampleRate, channelCount));
+ }
+
+ /**********************************************************************************************
+ * DISCONNECTED STATE TESTS *
+ *********************************************************************************************/
+
+ @Test
+ public void testConnectInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ mStateMachine.connect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mNativeInterface, timeout(TIMEOUT_MS).times(1)).connectA2dpSink(mDevice);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ }
+
+ @Test
+ public void testDisconnectInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ mStateMachine.disconnect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testAudioConfigChangedInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendAudioConfigChangedEvent(44, 1);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mStateMachine.getAudioConfig()).isNull();
+ }
+
+ @Test
+ public void testIncomingConnectedInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void testAllowedIncomingConnectionInDisconnected() {
+ mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ verify(mNativeInterface, times(0)).connectA2dpSink(mDevice);
+ }
+
+ @Test
+ public void testForbiddenIncomingConnectionInDisconnected() {
+ mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mNativeInterface, times(1)).disconnectA2dpSink(mDevice);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testUnknownIncomingConnectionInDisconnected() {
+ mockDeviceConnectionPolicy(mDevice, BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ verify(mNativeInterface, times(0)).connectA2dpSink(mDevice);
+ }
+
+ @Test
+ public void testIncomingDisconnectInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ }
+
+ @Test
+ public void testIncomingDisconnectingInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ verify(mService, times(0)).removeStateMachine(mStateMachine);
+ }
+
+ @Test
+ public void testIncomingConnectingInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testUnhandledMessageInDisconnected() {
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ mStateMachine.sendMessage(UNHANDLED_MESSAGE);
+ mStateMachine.sendMessage(UNHANDLED_MESSAGE, 0 /* arbitrary payload */);
+ }
+
+ /**********************************************************************************************
+ * CONNECTING STATE TESTS *
+ *********************************************************************************************/
+
+ @Test
+ public void testConnectedInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void testConnectingInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ }
+
+ @Test
+ public void testDisconnectingInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ }
+
+ @Test
+ public void testDisconnectedInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testConnectionTimeoutInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ verify(mService, timeout(CONNECT_TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testAudioStateChangeInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ sendAudioConfigChangedEvent(44, 1);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ assertThat(mStateMachine.getAudioConfig()).isNull();
+ }
+
+ @Test
+ public void testConnectInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ mStateMachine.connect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ }
+
+ @Test
+ public void testDisconnectInConnecting() {
+ testConnectInDisconnected();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ mStateMachine.disconnect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ }
+
+ /**********************************************************************************************
+ * CONNECTED STATE TESTS *
+ *********************************************************************************************/
+
+ @Test
+ public void testConnectInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ mStateMachine.connect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void testDisconnectInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ mStateMachine.disconnect();
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mNativeInterface, times(1)).disconnectA2dpSink(mDevice);
+ verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testAudioStateChangeInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ sendAudioConfigChangedEvent(44, 1);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ BluetoothAudioConfig expected =
+ new BluetoothAudioConfig(44, 1, AudioFormat.ENCODING_PCM_16BIT);
+ BluetoothAudioConfig config = mStateMachine.getAudioConfig();
+ assertThat(config).isEqualTo(expected);
+ }
+
+ @Test
+ public void testConnectedInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void testConnectingInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_CONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void testDisconnectingInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTING);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void testDisconnectedInConnected() {
+ testConnectedInConnecting();
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ sendConnectionEvent(BluetoothProfile.STATE_DISCONNECTED);
+ TestUtils.waitForLooperToFinishScheduledTask(mStateMachine.getHandler().getLooper());
+ verify(mService, timeout(TIMEOUT_MS).times(1)).removeStateMachine(mStateMachine);
+ assertThat(mStateMachine.getState()).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ /**********************************************************************************************
+ * OTHER TESTS *
+ *********************************************************************************************/
+
+ @Test
+ public void testDump() {
+ StringBuilder sb = new StringBuilder();
+ mStateMachine.dump(sb);
+ assertThat(sb.toString()).isNotNull();
+ }
+}
diff --git a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 34d228d..fde7dd6 100644
--- a/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -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;
@@ -69,6 +70,7 @@
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;
@@ -742,23 +744,50 @@
}
/**
- * 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++;
- verify(mHeadsetService, timeout(CONNECT_TIMEOUT_TEST_WAIT_MILLIS).times(
- numBroadcastsSent)).sendBroadcastAsUser(mIntentArgument.capture(),
- eq(UserHandle.ALL), eq(BLUETOOTH_CONNECT),
- any(Bundle.class));
- HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
- BluetoothHeadset.STATE_AUDIO_DISCONNECTED, BluetoothHeadset.STATE_AUDIO_CONNECTED,
- mIntentArgument.getValue());
- Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
- IsInstanceOf.instanceOf(HeadsetStateMachine.Connected.class));
+ 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));
+ if (i < MAX_RETRY_DISCONNECT_AUDIO) { // Test if state is AudioOn before max retry
+ HeadsetTestUtils.verifyAudioStateBroadcast(mTestDevice,
+ BluetoothHeadset.STATE_AUDIO_CONNECTED,
+ BluetoothHeadset.STATE_AUDIO_CONNECTED,
+ mIntentArgument.getValue());
+ Assert.assertThat(mHeadsetStateMachine.getCurrentState(),
+ 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));
+ }
+ }
}
/**