| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.a2dp; |
| |
| import static org.mockito.Mockito.*; |
| |
| import android.bluetooth.BluetoothA2dp; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothCodecConfig; |
| import android.bluetooth.BluetoothCodecStatus; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothUuid; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.media.AudioManager; |
| import android.media.BluetoothProfileConnectionInfo; |
| import android.os.Looper; |
| import android.os.ParcelUuid; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.rule.ServiceTestRule; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.bluetooth.TestUtils; |
| import com.android.bluetooth.btservice.ActiveDeviceManager; |
| import com.android.bluetooth.btservice.AdapterService; |
| import com.android.bluetooth.btservice.SilenceDeviceManager; |
| import com.android.bluetooth.btservice.storage.DatabaseManager; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.LinkedBlockingQueue; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class A2dpServiceTest { |
| private static final int MAX_CONNECTED_AUDIO_DEVICES = 5; |
| |
| private BluetoothAdapter mAdapter; |
| private Context mTargetContext; |
| private A2dpService mA2dpService; |
| private BluetoothDevice mTestDevice; |
| private static final int TIMEOUT_MS = 1000; // 1s |
| |
| private BroadcastReceiver mA2dpIntentReceiver; |
| private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>(); |
| private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>(); |
| private final BlockingQueue<Intent> mCodecConfigChangedQueue = new LinkedBlockingQueue<>(); |
| |
| @Mock private AdapterService mAdapterService; |
| @Mock private ActiveDeviceManager mActiveDeviceManager; |
| @Mock private A2dpNativeInterface mMockNativeInterface; |
| @Mock private DatabaseManager mDatabaseManager; |
| @Mock private SilenceDeviceManager mSilenceDeviceManager; |
| |
| @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); |
| |
| @Before |
| public void setUp() throws Exception { |
| mTargetContext = InstrumentationRegistry.getTargetContext(); |
| // Set up mocks and test assets |
| MockitoAnnotations.initMocks(this); |
| |
| if (Looper.myLooper() == null) { |
| Looper.prepare(); |
| } |
| |
| TestUtils.setAdapterService(mAdapterService); |
| doReturn(true).when(mAdapterService).isA2dpOffloadEnabled(); |
| doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices(); |
| doReturn(true, false).when(mAdapterService).isStartedProfile(anyString()); |
| doReturn(false).when(mAdapterService).isQuietModeEnabled(); |
| doReturn(mDatabaseManager).when(mAdapterService).getDatabase(); |
| doReturn(mActiveDeviceManager).when(mAdapterService).getActiveDeviceManager(); |
| doReturn(mSilenceDeviceManager).when(mAdapterService).getSilenceDeviceManager(); |
| |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| |
| mA2dpService = new A2dpService(mTargetContext, mMockNativeInterface); |
| mA2dpService.doStart(); |
| |
| // Override the timeout value to speed up the test |
| A2dpStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s |
| |
| // Set up the Connection State Changed receiver |
| IntentFilter filter = new IntentFilter(); |
| filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); |
| filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); |
| filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); |
| filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); |
| mA2dpIntentReceiver = new A2dpIntentReceiver(); |
| mTargetContext.registerReceiver(mA2dpIntentReceiver, filter); |
| |
| // Get a device for testing |
| mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); |
| doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) |
| .getBondState(any(BluetoothDevice.class)); |
| doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SINK}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| mA2dpService.doStop(); |
| mTargetContext.unregisterReceiver(mA2dpIntentReceiver); |
| mConnectionStateChangedQueue.clear(); |
| mAudioStateChangedQueue.clear(); |
| mCodecConfigChangedQueue.clear(); |
| TestUtils.clearAdapterService(mAdapterService); |
| } |
| |
| private class A2dpIntentReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { |
| try { |
| mConnectionStateChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Connection State queue: " |
| + e.getMessage()); |
| } |
| } |
| if (BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED.equals(intent.getAction())) { |
| try { |
| mAudioStateChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Audio State queue: " + e.getMessage()); |
| } |
| } |
| if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(intent.getAction())) { |
| try { |
| mCodecConfigChangedQueue.put(intent); |
| } catch (InterruptedException e) { |
| Assert.fail("Cannot add Intent to the Codec Config queue: " + e.getMessage()); |
| } |
| } |
| } |
| } |
| |
| private void verifyConnectionStateIntent(int timeoutMs, BluetoothDevice device, |
| int newState, int prevState) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mConnectionStateChangedQueue); |
| Assert.assertNotNull(intent); |
| Assert.assertEquals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, |
| intent.getAction()); |
| Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); |
| Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); |
| Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, |
| -1)); |
| } |
| |
| private void verifyNoConnectionStateIntent(int timeoutMs) { |
| Intent intent = TestUtils.waitForNoIntent(timeoutMs, mConnectionStateChangedQueue); |
| Assert.assertNull(intent); |
| } |
| |
| private void verifyAudioStateIntent(int timeoutMs, BluetoothDevice device, |
| int newState, int prevState) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mAudioStateChangedQueue); |
| Assert.assertNotNull(intent); |
| Assert.assertEquals(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED, intent.getAction()); |
| Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); |
| Assert.assertEquals(newState, intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)); |
| Assert.assertEquals(prevState, intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, |
| -1)); |
| } |
| |
| private void verifyNoAudioStateIntent(int timeoutMs) { |
| Intent intent = TestUtils.waitForNoIntent(timeoutMs, mAudioStateChangedQueue); |
| Assert.assertNull(intent); |
| } |
| |
| private void verifyCodecConfigIntent(int timeoutMs, BluetoothDevice device, |
| BluetoothCodecStatus codecStatus) { |
| Intent intent = TestUtils.waitForIntent(timeoutMs, mCodecConfigChangedQueue); |
| Assert.assertNotNull(intent); |
| Assert.assertEquals(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED, intent.getAction()); |
| Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)); |
| Assert.assertEquals(codecStatus, |
| intent.getParcelableExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS)); |
| } |
| |
| private void verifyNoCodecConfigIntent(int timeoutMs) { |
| Intent intent = TestUtils.waitForNoIntent(timeoutMs, mCodecConfigChangedQueue); |
| Assert.assertNull(intent); |
| } |
| |
| @Test |
| public void testGetA2dpService() { |
| Assert.assertEquals(mA2dpService, A2dpService.getA2dpService()); |
| } |
| |
| @Test |
| public void testStopA2dpService() { |
| // Prepare: connect and set active device |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| connectDevice(mTestDevice); |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| verify(mMockNativeInterface).setActiveDevice(mTestDevice); |
| |
| Assert.assertTrue(mA2dpService.stop()); |
| |
| // Verify that setActiveDevice(null) was called during shutdown |
| verify(mMockNativeInterface).setActiveDevice(null); |
| Assert.assertTrue(mA2dpService.start()); |
| } |
| |
| /** |
| * Test get priority for BluetoothDevice |
| */ |
| @Test |
| public void testGetPriority() { |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| Assert.assertEquals("Initial device priority", |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN, |
| mA2dpService.getConnectionPolicy(mTestDevice)); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| Assert.assertEquals("Setting device priority to PRIORITY_OFF", |
| BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, |
| mA2dpService.getConnectionPolicy(mTestDevice)); |
| |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| Assert.assertEquals("Setting device priority to PRIORITY_ON", |
| BluetoothProfile.CONNECTION_POLICY_ALLOWED, |
| mA2dpService.getConnectionPolicy(mTestDevice)); |
| } |
| |
| /** |
| * Test okToConnect method using various test cases |
| */ |
| @Test |
| public void testOkToConnect() { |
| int badPriorityValue = 1024; |
| int badBondState = 42; |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_NONE, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_NONE, badPriorityValue, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDING, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDING, badPriorityValue, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, true); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDED, BluetoothProfile.CONNECTION_POLICY_ALLOWED, true); |
| testOkToConnectCase(mTestDevice, |
| BluetoothDevice.BOND_BONDED, badPriorityValue, false); |
| testOkToConnectCase(mTestDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_UNKNOWN, false); |
| testOkToConnectCase(mTestDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, false); |
| testOkToConnectCase(mTestDevice, |
| badBondState, BluetoothProfile.CONNECTION_POLICY_ALLOWED, false); |
| testOkToConnectCase(mTestDevice, |
| badBondState, badPriorityValue, false); |
| } |
| |
| |
| /** |
| * Test that an outgoing connection to device that does not have A2DP Sink UUID is rejected |
| */ |
| @Test |
| public void testOutgoingConnectMissingAudioSinkUuid() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // Return AudioSource UUID instead of AudioSink |
| doReturn(new ParcelUuid[]{BluetoothUuid.A2DP_SOURCE}).when(mAdapterService) |
| .getRemoteUuids(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice)); |
| } |
| |
| /** |
| * Test that an outgoing connection to device with PRIORITY_OFF is rejected |
| */ |
| @Test |
| public void testOutgoingConnectPriorityOff() { |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // Set the device priority to PRIORITY_OFF so connect() should fail |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); |
| |
| // Send a connect request |
| Assert.assertFalse("Connect expected to fail", mA2dpService.connect(mTestDevice)); |
| } |
| |
| /** |
| * Test that an outgoing connection times out |
| */ |
| @Test |
| public void testOutgoingConnectTimeout() { |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice)); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(A2dpStateMachine.sConnectTimeoutMs * 2, |
| mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| } |
| |
| /** |
| * Test that an outgoing connection/disconnection succeeds |
| */ |
| @Test |
| public void testOutgoingConnectDisconnectSuccess() { |
| A2dpStackEvent connCompletedEvent; |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // Send a connect request |
| Assert.assertTrue("Connect failed", mA2dpService.connect(mTestDevice)); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| |
| // Send a message to trigger connection completed |
| connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mTestDevice; |
| connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; |
| mA2dpService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| |
| // Verify the list of connected devices |
| Assert.assertTrue(mA2dpService.getConnectedDevices().contains(mTestDevice)); |
| |
| // Send a disconnect request |
| Assert.assertTrue("Disconnect failed", mA2dpService.disconnect(mTestDevice)); |
| |
| // Verify the connection state broadcast, and that we are in Disconnecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| |
| // Send a message to trigger disconnection completed |
| connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = mTestDevice; |
| connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_DISCONNECTED; |
| mA2dpService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Disconnected state |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| |
| // Verify the list of connected devices |
| Assert.assertFalse(mA2dpService.getConnectedDevices().contains(mTestDevice)); |
| } |
| |
| /** |
| * Test that an outgoing connection/disconnection succeeds |
| */ |
| @Test |
| public void testMaxConnectDevices() { |
| A2dpStackEvent connCompletedEvent; |
| BluetoothDevice[] testDevices = new BluetoothDevice[MAX_CONNECTED_AUDIO_DEVICES]; |
| BluetoothDevice extraTestDevice; |
| |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // Prepare and connect all test devices |
| for (int i = 0; i < MAX_CONNECTED_AUDIO_DEVICES; i++) { |
| BluetoothDevice testDevice = TestUtils.getTestDevice(mAdapter, i); |
| testDevices[i] = testDevice; |
| when(mDatabaseManager.getProfileConnectionPolicy(testDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| // Send a connect request |
| Assert.assertTrue("Connect failed", mA2dpService.connect(testDevice)); |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(testDevice)); |
| // Send a message to trigger connection completed |
| connCompletedEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = testDevice; |
| connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; |
| mA2dpService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, testDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(testDevice)); |
| // Verify the list of connected devices |
| Assert.assertTrue(mA2dpService.getConnectedDevices().contains(testDevice)); |
| } |
| |
| // Prepare and connect the extra test device. The connect request should fail |
| extraTestDevice = TestUtils.getTestDevice(mAdapter, MAX_CONNECTED_AUDIO_DEVICES); |
| when(mDatabaseManager.getProfileConnectionPolicy(extraTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| // Send a connect request |
| Assert.assertFalse("Connect expected to fail", mA2dpService.connect(extraTestDevice)); |
| } |
| |
| /** |
| * Test that only CONNECTION_STATE_CONNECTED or CONNECTION_STATE_CONNECTING A2DP stack events |
| * will create a state machine. |
| */ |
| @Test |
| public void testCreateStateMachineStackEvents() { |
| A2dpStackEvent stackEvent; |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine should not be created |
| generateUnexpectedConnectionMessageFromNative(mTestDevice, |
| BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should not be created |
| generateUnexpectedConnectionMessageFromNative(mTestDevice, |
| BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| } |
| |
| /** |
| * Test that EVENT_TYPE_AUDIO_STATE_CHANGED and EVENT_TYPE_CODEC_CONFIG_CHANGED events |
| * are processed. |
| */ |
| @Test |
| public void testProcessAudioStateChangedCodecConfigChangedEvents() { |
| A2dpStackEvent stackEvent; |
| BluetoothCodecConfig codecConfigSbc = |
| buildBluetoothCodecConfig( |
| BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, |
| BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, |
| BluetoothCodecConfig.SAMPLE_RATE_44100, |
| BluetoothCodecConfig.BITS_PER_SAMPLE_16, |
| BluetoothCodecConfig.CHANNEL_MODE_STEREO, |
| 0, 0, 0, 0); // Codec-specific fields |
| BluetoothCodecConfig codecConfig = codecConfigSbc; |
| BluetoothCodecConfig[] codecsLocalCapabilities = new BluetoothCodecConfig[1]; |
| BluetoothCodecConfig[] codecsSelectableCapabilities = new BluetoothCodecConfig[1]; |
| codecsLocalCapabilities[0] = codecConfigSbc; |
| codecsSelectableCapabilities[0] = codecConfigSbc; |
| BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfig, |
| Arrays.asList(codecsLocalCapabilities), |
| Arrays.asList(codecsSelectableCapabilities)); |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - state machine should not be created |
| generateUnexpectedAudioMessageFromNative(mTestDevice, A2dpStackEvent.AUDIO_STATE_STARTED, |
| BluetoothA2dp.STATE_PLAYING, |
| BluetoothA2dp.STATE_NOT_PLAYING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - state machine should not be created |
| generateUnexpectedCodecMessageFromNative(mTestDevice, codecStatus); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine should be created |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: EVENT_TYPE_AUDIO_STATE_CHANGED - Intent broadcast should be generated |
| // NOTE: The first message (STATE_PLAYING -> STATE_NOT_PLAYING) is generated internally |
| // by the state machine when Connected, and needs to be extracted first before generating |
| // the actual message from native. |
| verifyAudioStateIntent(TIMEOUT_MS, mTestDevice, BluetoothA2dp.STATE_NOT_PLAYING, |
| BluetoothA2dp.STATE_PLAYING); |
| generateAudioMessageFromNative(mTestDevice, |
| A2dpStackEvent.AUDIO_STATE_STARTED, |
| BluetoothA2dp.STATE_PLAYING, |
| BluetoothA2dp.STATE_NOT_PLAYING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: EVENT_TYPE_CODEC_CONFIG_CHANGED - Intent broadcast should be generated |
| generateCodecMessageFromNative(mTestDevice, codecStatus); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine should be removed |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| } |
| |
| /** |
| * Test that a state machine in DISCONNECTED state is removed only after the device is unbond. |
| */ |
| @Test |
| public void testDeleteStateMachineUnbondEvents() { |
| A2dpStackEvent stackEvent; |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| // Device unbond - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| // Device unbond - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| // Device unbond - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_BONDED); |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_DISCONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| // Device unbond - state machine is removed |
| mA2dpService.bondStateChanged(mTestDevice, BluetoothDevice.BOND_NONE); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| } |
| |
| /** |
| * Test that a CONNECTION_STATE_DISCONNECTED A2DP stack event will remove the state machine |
| * only if the device is unbond. |
| */ |
| @Test |
| public void testDeleteStateMachineDisconnectEvents() { |
| A2dpStackEvent stackEvent; |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(mTestDevice, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(any(BluetoothDevice.class)); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(any(BluetoothDevice.class)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine should be created |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_CONNECTING - state machine remains |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // Device bond state marked as unbond - state machine is not removed |
| doReturn(BluetoothDevice.BOND_NONE).when(mAdapterService) |
| .getBondState(any(BluetoothDevice.class)); |
| Assert.assertTrue(mA2dpService.getDevices().contains(mTestDevice)); |
| |
| // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is removed |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED, |
| mA2dpService.getConnectionState(mTestDevice)); |
| Assert.assertFalse(mA2dpService.getDevices().contains(mTestDevice)); |
| } |
| |
| /** |
| * Test that whether active device been removed after enable silence mode |
| */ |
| @Test |
| public void testSetSilenceMode() { |
| BluetoothDevice otherDevice = mAdapter.getRemoteDevice("05:04:03:02:01:00"); |
| connectDevice(mTestDevice); |
| connectDevice(otherDevice); |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| doReturn(true) |
| .when(mMockNativeInterface) |
| .setSilenceDevice(any(BluetoothDevice.class), anyBoolean()); |
| |
| // Test whether active device been removed after enable silence mode. |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, true)); |
| verify(mMockNativeInterface).setSilenceDevice(mTestDevice, true); |
| Assert.assertNull(mA2dpService.getActiveDevice()); |
| |
| // Test whether active device been resumeed after disable silence mode. |
| Assert.assertTrue(mA2dpService.setSilenceMode(mTestDevice, false)); |
| verify(mMockNativeInterface).setSilenceDevice(mTestDevice, false); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| |
| // Test that active device should not be changed when silence a non-active device |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, true)); |
| verify(mMockNativeInterface).setSilenceDevice(otherDevice, true); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| |
| // Test that active device should not be changed when another device exits silence mode |
| Assert.assertTrue(mA2dpService.setSilenceMode(otherDevice, false)); |
| verify(mMockNativeInterface).setSilenceDevice(otherDevice, false); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| } |
| |
| @Test |
| public void testSetActiveDevice_withNull_returnsFalse() { |
| // Null is not accepted. |
| Assert.assertFalse(mA2dpService.setActiveDevice(null)); |
| } |
| |
| /** |
| * Test whether removeActiveDevice(false) suppresses noisy intent. |
| * Music should keep playing. |
| * (e.g. user selected LE headset via UI) |
| */ |
| @Test |
| public void testRemoveActiveDevice_whenStopAudioIsFalse_suppressNoisyIntent() { |
| connectDevice(mTestDevice); |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| AudioManager audioManager = mock(AudioManager.class); |
| mA2dpService.mAudioManager = audioManager; |
| |
| Assert.assertTrue(mA2dpService.disconnect(mTestDevice)); |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| mA2dpService.removeActiveDevice(false); |
| |
| ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = |
| ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); |
| verify(audioManager).handleBluetoothActiveDeviceChanged( |
| isNull(), eq(mTestDevice), connectionInfoArgumentCaptor.capture()); |
| BluetoothProfileConnectionInfo connectionInfo = |
| connectionInfoArgumentCaptor.getValue(); |
| // Should suppress noisy intent. (i.e. Music should keep playing) |
| Assert.assertTrue(connectionInfo.isSuppressNoisyIntent()); |
| } |
| |
| /** |
| * Test whether removeActiveDevice(true) does not suppress noisy intent. |
| * Music should pause. |
| * (e.g. The solely connected BT device is disconnected) |
| */ |
| @Test |
| public void testRemoveActiveDevice_whenStopAudioIsFalse_doesNotSuppressNoisyIntent() { |
| connectDevice(mTestDevice); |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| AudioManager audioManager = mock(AudioManager.class); |
| mA2dpService.mAudioManager = audioManager; |
| |
| Assert.assertTrue(mA2dpService.disconnect(mTestDevice)); |
| verifyConnectionStateIntent(TIMEOUT_MS, mTestDevice, BluetoothProfile.STATE_DISCONNECTING, |
| BluetoothProfile.STATE_CONNECTED); |
| mA2dpService.removeActiveDevice(true); |
| |
| ArgumentCaptor<BluetoothProfileConnectionInfo> connectionInfoArgumentCaptor = |
| ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class); |
| verify(audioManager).handleBluetoothActiveDeviceChanged( |
| isNull(), eq(mTestDevice), connectionInfoArgumentCaptor.capture()); |
| BluetoothProfileConnectionInfo connectionInfo = |
| connectionInfoArgumentCaptor.getValue(); |
| // Should not suppress noisy intent. (i.e. Music should pause) |
| Assert.assertFalse(connectionInfo.isSuppressNoisyIntent()); |
| } |
| |
| /** |
| * Test that whether updateOptionalCodecsSupport() method is working as intended |
| * when a Bluetooth device is connected with A2DP. |
| */ |
| @Test |
| public void testUpdateOptionalCodecsSupport() { |
| int verifySupportTime = 0; |
| int verifyNotSupportTime = 0; |
| int verifyEnabledTime = 0; |
| // Test for device supports optional codec |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| ++verifySupportTime, verifyNotSupportTime, ++verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, true, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| ++verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| |
| // Test for device not supports optional codec |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| verifySupportTime, ++verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN, |
| verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED, |
| verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| testUpdateOptionalCodecsSupportCase( |
| BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED, false, |
| BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED, |
| verifySupportTime, verifyNotSupportTime, verifyEnabledTime); |
| } |
| |
| /** |
| * Tests that {@link A2dpService#sendPreferredAudioProfileChangeToAudioFramework()} sends |
| * requests to the audio framework when there is an active A2DP device. |
| */ |
| @Test |
| public void testSendPreferredAudioProfileChangeToAudioFramework() { |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| Assert.assertTrue(mA2dpService.removeActiveDevice(true)); |
| Assert.assertNull(mA2dpService.getActiveDevice()); |
| |
| // Send 0 requests when the active device is null |
| Assert.assertEquals(0, mA2dpService.sendPreferredAudioProfileChangeToAudioFramework()); |
| |
| // Send 1 request when there is an A2DP active device |
| connectDevice(mTestDevice); |
| Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice)); |
| Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice()); |
| Assert.assertEquals(1, mA2dpService.sendPreferredAudioProfileChangeToAudioFramework()); |
| } |
| |
| @Test |
| public void testDumpDoesNotCrash() { |
| mA2dpService.dump(new StringBuilder()); |
| } |
| |
| private void connectDevice(BluetoothDevice device) { |
| connectDeviceWithCodecStatus(device, null); |
| } |
| |
| private void connectDeviceWithCodecStatus(BluetoothDevice device, |
| BluetoothCodecStatus codecStatus) { |
| A2dpStackEvent connCompletedEvent; |
| |
| List<BluetoothDevice> prevConnectedDevices = mA2dpService.getConnectedDevices(); |
| |
| // Update the device priority so okToConnect() returns true |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP)) |
| .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); |
| doReturn(true).when(mMockNativeInterface).connectA2dp(device); |
| doReturn(true).when(mMockNativeInterface).disconnectA2dp(device); |
| doReturn(true) |
| .when(mMockNativeInterface) |
| .setCodecConfigPreference( |
| any(BluetoothDevice.class), any(BluetoothCodecConfig[].class)); |
| |
| // Send a connect request |
| Assert.assertTrue("Connect failed", mA2dpService.connect(device)); |
| |
| // Verify the connection state broadcast, and that we are in Connecting state |
| verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTING, |
| BluetoothProfile.STATE_DISCONNECTED); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, |
| mA2dpService.getConnectionState(device)); |
| |
| if (codecStatus != null) { |
| generateCodecMessageFromNative(device, codecStatus); |
| } |
| |
| // Send a message to trigger connection completed |
| connCompletedEvent = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| connCompletedEvent.device = device; |
| connCompletedEvent.valueInt = A2dpStackEvent.CONNECTION_STATE_CONNECTED; |
| mA2dpService.messageFromNative(connCompletedEvent); |
| |
| // Verify the connection state broadcast, and that we are in Connected state |
| verifyConnectionStateIntent(TIMEOUT_MS, device, BluetoothProfile.STATE_CONNECTED, |
| BluetoothProfile.STATE_CONNECTING); |
| Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, |
| mA2dpService.getConnectionState(device)); |
| |
| // Verify that the device is in the list of connected devices |
| Assert.assertTrue(mA2dpService.getConnectedDevices().contains(device)); |
| // Verify the list of previously connected devices |
| for (BluetoothDevice prevDevice : prevConnectedDevices) { |
| Assert.assertTrue(mA2dpService.getConnectedDevices().contains(prevDevice)); |
| } |
| } |
| |
| private void generateConnectionMessageFromNative(BluetoothDevice device, int newConnectionState, |
| int oldConnectionState) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt = newConnectionState; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the connection state broadcast |
| verifyConnectionStateIntent(TIMEOUT_MS, device, newConnectionState, oldConnectionState); |
| } |
| |
| private void generateUnexpectedConnectionMessageFromNative(BluetoothDevice device, |
| int newConnectionState, |
| int oldConnectionState) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt = newConnectionState; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the connection state broadcast |
| verifyNoConnectionStateIntent(TIMEOUT_MS); |
| } |
| |
| private void generateAudioMessageFromNative(BluetoothDevice device, int audioStackEvent, |
| int newAudioState, int oldAudioState) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt = audioStackEvent; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the audio state broadcast |
| verifyAudioStateIntent(TIMEOUT_MS, device, newAudioState, oldAudioState); |
| } |
| |
| private void generateUnexpectedAudioMessageFromNative(BluetoothDevice device, |
| int audioStackEvent, int newAudioState, |
| int oldAudioState) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); |
| stackEvent.device = device; |
| stackEvent.valueInt = audioStackEvent; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the audio state broadcast |
| verifyNoAudioStateIntent(TIMEOUT_MS); |
| } |
| |
| private void generateCodecMessageFromNative(BluetoothDevice device, |
| BluetoothCodecStatus codecStatus) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); |
| stackEvent.device = device; |
| stackEvent.codecStatus = codecStatus; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the codec status broadcast |
| verifyCodecConfigIntent(TIMEOUT_MS, device, codecStatus); |
| } |
| |
| private void generateUnexpectedCodecMessageFromNative(BluetoothDevice device, |
| BluetoothCodecStatus codecStatus) { |
| A2dpStackEvent stackEvent = |
| new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); |
| stackEvent.device = device; |
| stackEvent.codecStatus = codecStatus; |
| mA2dpService.messageFromNative(stackEvent); |
| // Verify the codec status broadcast |
| verifyNoCodecConfigIntent(TIMEOUT_MS); |
| } |
| |
| /** |
| * Helper function to test okToConnect() method. |
| * |
| * @param device test device |
| * @param bondState bond state value, could be invalid |
| * @param priority value, could be invalid, coudl be invalid |
| * @param expected expected result from okToConnect() |
| */ |
| private void testOkToConnectCase(BluetoothDevice device, int bondState, int priority, |
| boolean expected) { |
| doReturn(bondState).when(mAdapterService).getBondState(device); |
| when(mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.A2DP)) |
| .thenReturn(priority); |
| |
| // Test when the AdapterService is in non-quiet mode: the result should not depend |
| // on whether the connection request is outgoing or incoming. |
| doReturn(false).when(mAdapterService).isQuietModeEnabled(); |
| Assert.assertEquals(expected, mA2dpService.okToConnect(device, true)); // Outgoing |
| Assert.assertEquals(expected, mA2dpService.okToConnect(device, false)); // Incoming |
| |
| // Test when the AdapterService is in quiet mode: the result should always be |
| // false when the connection request is incoming. |
| doReturn(true).when(mAdapterService).isQuietModeEnabled(); |
| Assert.assertEquals(expected, mA2dpService.okToConnect(device, true)); // Outgoing |
| Assert.assertEquals(false, mA2dpService.okToConnect(device, false)); // Incoming |
| } |
| |
| /** |
| * Helper function to test updateOptionalCodecsSupport() method |
| * |
| * @param previousSupport previous optional codec support status |
| * @param support new optional codec support status |
| * @param previousEnabled previous optional codec enable status |
| * @param verifySupportTime verify times of optional codec set to support |
| * @param verifyNotSupportTime verify times of optional codec set to not support |
| * @param verifyEnabledTime verify times of optional codec set to enabled |
| */ |
| private void testUpdateOptionalCodecsSupportCase(int previousSupport, boolean support, |
| int previousEnabled, int verifySupportTime, int verifyNotSupportTime, |
| int verifyEnabledTime) { |
| doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class)); |
| |
| BluetoothCodecConfig codecConfigSbc = |
| buildBluetoothCodecConfig( |
| BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, |
| BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, |
| BluetoothCodecConfig.SAMPLE_RATE_44100, |
| BluetoothCodecConfig.BITS_PER_SAMPLE_16, |
| BluetoothCodecConfig.CHANNEL_MODE_STEREO, |
| 0, 0, 0, 0); // Codec-specific fields |
| BluetoothCodecConfig codecConfigAac = |
| buildBluetoothCodecConfig( |
| BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, |
| BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, |
| BluetoothCodecConfig.SAMPLE_RATE_44100, |
| BluetoothCodecConfig.BITS_PER_SAMPLE_16, |
| BluetoothCodecConfig.CHANNEL_MODE_STEREO, |
| 0, 0, 0, 0); // Codec-specific fields |
| |
| BluetoothCodecConfig[] codecsLocalCapabilities; |
| BluetoothCodecConfig[] codecsSelectableCapabilities; |
| if (support) { |
| codecsLocalCapabilities = new BluetoothCodecConfig[2]; |
| codecsSelectableCapabilities = new BluetoothCodecConfig[2]; |
| codecsLocalCapabilities[0] = codecConfigSbc; |
| codecsLocalCapabilities[1] = codecConfigAac; |
| codecsSelectableCapabilities[0] = codecConfigSbc; |
| codecsSelectableCapabilities[1] = codecConfigAac; |
| } else { |
| codecsLocalCapabilities = new BluetoothCodecConfig[1]; |
| codecsSelectableCapabilities = new BluetoothCodecConfig[1]; |
| codecsLocalCapabilities[0] = codecConfigSbc; |
| codecsSelectableCapabilities[0] = codecConfigSbc; |
| } |
| BluetoothCodecConfig[] badCodecsSelectableCapabilities; |
| badCodecsSelectableCapabilities = new BluetoothCodecConfig[1]; |
| badCodecsSelectableCapabilities[0] = codecConfigAac; |
| |
| BluetoothCodecStatus codecStatus = new BluetoothCodecStatus(codecConfigSbc, |
| Arrays.asList(codecsLocalCapabilities), |
| Arrays.asList(codecsSelectableCapabilities)); |
| BluetoothCodecStatus badCodecStatus = new BluetoothCodecStatus(codecConfigAac, |
| Arrays.asList(codecsLocalCapabilities), |
| Arrays.asList(badCodecsSelectableCapabilities)); |
| |
| when(mDatabaseManager.getA2dpSupportsOptionalCodecs(mTestDevice)) |
| .thenReturn(previousSupport); |
| when(mDatabaseManager.getA2dpOptionalCodecsEnabled(mTestDevice)) |
| .thenReturn(previousEnabled); |
| |
| // Generate connection request from native with bad codec status |
| connectDeviceWithCodecStatus(mTestDevice, badCodecStatus); |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| |
| // Generate connection request from native with good codec status |
| connectDeviceWithCodecStatus(mTestDevice, codecStatus); |
| generateConnectionMessageFromNative(mTestDevice, BluetoothProfile.STATE_DISCONNECTED, |
| BluetoothProfile.STATE_CONNECTED); |
| |
| // Check optional codec status is set properly |
| verify(mDatabaseManager, times(verifyNotSupportTime)).setA2dpSupportsOptionalCodecs( |
| mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); |
| verify(mDatabaseManager, times(verifySupportTime)).setA2dpSupportsOptionalCodecs( |
| mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); |
| verify(mDatabaseManager, times(verifyEnabledTime)).setA2dpOptionalCodecsEnabled( |
| mTestDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); |
| } |
| |
| private BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, |
| int codecPriority, int sampleRate, int bitsPerSample, int channelMode, |
| long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { |
| return new BluetoothCodecConfig.Builder() |
| .setCodecType(sourceCodecType) |
| .setCodecPriority(codecPriority) |
| .setSampleRate(sampleRate) |
| .setBitsPerSample(bitsPerSample) |
| .setChannelMode(channelMode) |
| .setCodecSpecific1(codecSpecific1) |
| .setCodecSpecific2(codecSpecific2) |
| .setCodecSpecific3(codecSpecific3) |
| .setCodecSpecific4(codecSpecific4) |
| .build(); |
| } |
| } |