| /* |
| * Copyright 2019 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.server.audio; |
| |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.anyInt; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothProfile; |
| import android.content.Context; |
| import android.media.AudioManager; |
| import android.media.AudioSystem; |
| import android.util.Log; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.MediumTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentMatchers; |
| import org.mockito.Mock; |
| import org.mockito.Spy; |
| |
| @MediumTest |
| @RunWith(AndroidJUnit4.class) |
| public class AudioDeviceBrokerTest { |
| |
| private static final String TAG = "AudioDeviceBrokerTest"; |
| private static final int MAX_MESSAGE_HANDLING_DELAY_MS = 100; |
| |
| private Context mContext; |
| // the actual class under test |
| private AudioDeviceBroker mAudioDeviceBroker; |
| |
| @Mock private AudioService mMockAudioService; |
| @Spy private AudioDeviceInventory mSpyDevInventory; |
| @Spy private AudioSystemAdapter mSpyAudioSystem; |
| |
| private BluetoothDevice mFakeBtDevice; |
| |
| @Before |
| public void setUp() throws Exception { |
| mContext = InstrumentationRegistry.getTargetContext(); |
| |
| mMockAudioService = mock(AudioService.class); |
| mSpyAudioSystem = spy(AudioSystemAdapter.getAlwaysOkAdapter()); |
| mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem)); |
| mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory); |
| mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker); |
| |
| BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); |
| mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05"); |
| Assert.assertNotNull("invalid null BT device", mFakeBtDevice); |
| } |
| |
| @After |
| public void tearDown() throws Exception { } |
| |
| @Test |
| public void testSetUpAndTearDown() { } |
| |
| /** |
| * postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection: |
| * - verify it calls into AudioDeviceInventory with the right params |
| * - verify it calls into AudioSystem and stays connected (no 2nd call to disconnect) |
| * @throws Exception |
| */ |
| @Test |
| public void testPostA2dpDeviceConnectionChange() throws Exception { |
| Log.i(TAG, "testPostA2dpDeviceConnectionChange"); |
| Assert.assertNotNull("invalid null BT device", mFakeBtDevice); |
| |
| mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1); |
| Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS); |
| verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState( |
| any(BluetoothDevice.class), |
| ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED) /*state*/, |
| ArgumentMatchers.eq(BluetoothProfile.A2DP) /*profile*/, |
| ArgumentMatchers.eq(true) /*suppressNoisyIntent*/, anyInt() /*musicDevice*/, |
| ArgumentMatchers.eq(1) /*a2dpVolume*/ |
| ); |
| |
| final String expectedName = mFakeBtDevice.getName() == null ? "" : mFakeBtDevice.getName(); |
| verify(mSpyAudioSystem, times(1)).setDeviceConnectionState( |
| ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP), |
| ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE), |
| ArgumentMatchers.eq(mFakeBtDevice.getAddress()), |
| ArgumentMatchers.eq(expectedName), |
| anyInt() /*codec*/); |
| } |
| |
| /** |
| * Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for |
| * connection > pause > disconnection > connection |
| * keeps the device connected |
| * @throws Exception |
| */ |
| @Test |
| public void testA2dpDeviceConnectionDisconnectionConnectionChange() throws Exception { |
| Log.i(TAG, "testA2dpDeviceConnectionDisconnectionConnectionChange"); |
| |
| doTestConnectionDisconnectionReconnection(0); |
| } |
| |
| /** |
| * Verify device disconnection and reconnection within the BECOMING_NOISY window |
| * @throws Exception |
| */ |
| @Test |
| public void testA2dpDeviceReconnectionWithinBecomingNoisyDelay() throws Exception { |
| Log.i(TAG, "testA2dpDeviceReconnectionWithinBecomingNoisyDelay"); |
| |
| doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2); |
| } |
| |
| private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection) |
| throws Exception { |
| when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC)) |
| .thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); |
| when(mMockAudioService.isInCommunication()).thenReturn(false); |
| when(mMockAudioService.hasMediaDynamicPolicy()).thenReturn(false); |
| when(mMockAudioService.hasAudioFocusUsers()).thenReturn(false); |
| |
| // first connection |
| mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1); |
| Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); |
| |
| // disconnection |
| mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice, |
| BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1); |
| if (delayAfterDisconnection > 0) { |
| Thread.sleep(delayAfterDisconnection); |
| } |
| |
| // reconnection |
| mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice, |
| BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2); |
| Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS); |
| |
| // Verify disconnection has been cancelled and we're seeing two connections attempts, |
| // with the device connected at the end of the test |
| verify(mSpyDevInventory, times(2)).onSetA2dpSinkConnectionState( |
| any(BtHelper.BluetoothA2dpDeviceInfo.class), |
| ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED)); |
| Assert.assertTrue("Mock device not connected", |
| mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice)); |
| } |
| } |