Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a1e0582e authored by Vlad Popa's avatar Vlad Popa
Browse files

Add tests for the automatic audio device categorization

Testing the forwarded AudioDeviceBroker methods calls that query and
modify the internal AdiDeviceState objects which store the audio device
categorization information

Test: adb shell device_config put media_audio android.media.audio.automatic_bt_device_type true
Test: atest AudioDeviceBrokerTest
Bug: 302323921
Change-Id: I6f0b3a50fd6ad2c16ad4b5bedd0319414d28a417
parent 39917599
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import static android.media.AudioSystem.DEVICE_NONE;
import static android.media.AudioSystem.isBluetoothDevice;
import static android.media.audio.Flags.automaticBtDeviceType;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioDeviceAttributes;
@@ -31,13 +33,16 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Objects;

/**
 * Class representing all devices that were previously or are currently connected. Data is
 * persisted in {@link android.provider.Settings.Secure}
 */
/*package*/ final class AdiDeviceState {
@VisibleForTesting(visibility = PACKAGE)
public final class AdiDeviceState {
    private static final String TAG = "AS.AdiDeviceState";

    private static final String SETTING_FIELD_SEPARATOR = ",";
+4 −1
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ import static android.media.AudioSystem.isBluetoothScoOutDevice;
import static android.media.audio.Flags.automaticBtDeviceType;


import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
@@ -347,7 +349,8 @@ public class AudioDeviceInventory {
     * @return the found {@link AdiDeviceState} or {@code null} otherwise.
     */
    @Nullable
    AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
    @VisibleForTesting(visibility = PACKAGE)
    public AdiDeviceState findBtDeviceStateForAddress(String address, int deviceType) {
        Set<Integer> deviceSet;
        if (isBluetoothA2dpOutDevice(deviceType)) {
            deviceSet = DEVICE_OUT_ALL_A2DP_SET;
+6 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.bluetoothMacAddressAnonymization;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
@@ -6785,7 +6786,8 @@ public class AudioService extends IAudioService.Stub
        return mContentResolver;
    }
    /*package*/ SettingsAdapter getSettings() {
    @VisibleForTesting(visibility = PACKAGE)
    public SettingsAdapter getSettings() {
        return mSettings;
    }
@@ -11250,7 +11252,9 @@ public class AudioService extends IAudioService.Stub
        return mDeviceBroker.isBluetoothAudioDeviceCategoryFixed(address);
    }
    /*package*/void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
    /** Update the sound dose and spatializer state based on the new AdiDeviceState. */
    @VisibleForTesting(visibility = PACKAGE)
    public void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
        if (deviceState == null) {
            return;
        }
+125 −7
Original line number Diff line number Diff line
@@ -15,14 +15,28 @@
 */
package com.android.server.audio;

import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET;
import static android.media.audio.Flags.automaticBtDeviceType;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
@@ -40,10 +54,10 @@ import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;

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;

@@ -70,6 +84,9 @@ public class AudioDeviceBrokerTest {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();

        mMockAudioService = mock(AudioService.class);
        SettingsAdapter mockAdapter = mock(SettingsAdapter.class);
        when(mMockAudioService.getSettings()).thenReturn(mockAdapter);
        when(mockAdapter.getSecureStringForUser(any(), any(), anyInt())).thenReturn("");
        when(mMockAudioService.getBluetoothContextualVolumeStream())
                .thenReturn(AudioSystem.STREAM_MUSIC);

@@ -82,7 +99,6 @@ public class AudioDeviceBrokerTest {

        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        mFakeBtDevice = adapter.getRemoteDevice("00:01:02:03:04:05");
        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
    }

    @After
@@ -100,15 +116,14 @@ public class AudioDeviceBrokerTest {
    @Test
    public void testPostA2dpDeviceConnectionChange() throws Exception {
        Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
        assertNotNull("invalid null BT device", mFakeBtDevice);

        mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
                new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
                    BluetoothProfileConnectionInfo.createA2dpInfo(true, 1), "testSource"));
        Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
        verify(mSpyDevInventory, times(1)).setBluetoothActiveDevice(
                any(AudioDeviceBroker.BtDeviceInfo.class)
        );
                any(AudioDeviceBroker.BtDeviceInfo.class));

        // verify the connection was reported to AudioSystem
        checkSingleSystemConnection(mFakeBtDevice);
@@ -212,7 +227,7 @@ public class AudioDeviceBrokerTest {
                    AudioManager.DEVICE_OUT_SPEAKER, null);
            new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                    AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, null);
            Assert.fail();
            fail();
        } catch (NullPointerException e) { }
    }

@@ -228,11 +243,114 @@ public class AudioDeviceBrokerTest {
        final AdiDeviceState result = AdiDeviceState.fromPersistedString(persistString);
        Log.i(TAG, "original:" + devState);
        Log.i(TAG, "result  :" + result);
        Assert.assertEquals(devState, result);
        assertEquals(devState, result);
    }

    @Test
    public void testIsBluetoothAudioDeviceCategoryFixed() throws Exception {
        Log.i(TAG, "starting testIsBluetoothAudioDeviceCategoryFixed");

        if (!automaticBtDeviceType()) {
            Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
                    + "testIsBluetoothAudioDeviceCategoryFixed");
            return;
        }
        assertNotNull("invalid null BT device", mFakeBtDevice);

        final AdiDeviceState devState = new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, mFakeBtDevice.getAddress());
        doReturn(devState).when(mSpyDevInventory).findBtDeviceStateForAddress(
                mFakeBtDevice.getAddress(), AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
        try {
            InstrumentationRegistry.getInstrumentation().getUiAutomation()
                    .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);

            // no metadata set
            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
                    DEVICE_TYPE_DEFAULT.getBytes()));
            assertFalse(
                    mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
                            mFakeBtDevice.getAddress()));

            // metadata set
            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
                    DEVICE_TYPE_HEADSET.getBytes()));
            assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed(
                    mFakeBtDevice.getAddress()));
        } finally {
            InstrumentationRegistry.getInstrumentation().getUiAutomation()
                    .dropShellPermissionIdentity();
        }
    }

    @Test
    public void testGetAndUpdateBtAdiDeviceStateCategoryForAddress() throws Exception {
        Log.i(TAG, "starting testGetAndUpdateBtAdiDeviceStateCategoryForAddress");

        if (!automaticBtDeviceType()) {
            Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
                    + "testGetAndUpdateBtAdiDeviceStateCategoryForAddress");
            return;
        }
        assertNotNull("invalid null BT device", mFakeBtDevice);

        final AdiDeviceState devState = new AdiDeviceState(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                AudioManager.DEVICE_OUT_BLUETOOTH_A2DP, mFakeBtDevice.getAddress());
        devState.setAudioDeviceCategory(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
        doReturn(devState).when(mSpyDevInventory).findBtDeviceStateForAddress(
                eq(mFakeBtDevice.getAddress()), anyInt());
        try {
            InstrumentationRegistry.getInstrumentation().getUiAutomation()
                    .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED);

            // no metadata set
            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
                    DEVICE_TYPE_DEFAULT.getBytes()));
            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER,
                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
                            mFakeBtDevice.getAddress()));
            verify(mMockAudioService,
                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
                    eq(devState));

            // metadata set
            assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE,
                    DEVICE_TYPE_HEADSET.getBytes()));
            assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES,
                    mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress(
                            mFakeBtDevice.getAddress()));
            verify(mMockAudioService,
                    timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
                    any());
        } finally {
            InstrumentationRegistry.getInstrumentation().getUiAutomation()
                    .dropShellPermissionIdentity();
        }
    }

    @Test
    public void testAddAudioDeviceWithCategoryInInventoryIfNeeded() throws Exception {
        Log.i(TAG, "starting testAddAudioDeviceWithCategoryInInventoryIfNeeded");

        if (!automaticBtDeviceType()) {
            Log.i(TAG, "Enable automaticBtDeviceType flag to run the test "
                    + "testAddAudioDeviceWithCategoryInInventoryIfNeeded");
            return;
        }
        assertNotNull("invalid null BT device", mFakeBtDevice);

        mAudioDeviceBroker.addAudioDeviceWithCategoryInInventoryIfNeeded(
                mFakeBtDevice.getAddress(), AudioManager.AUDIO_DEVICE_CATEGORY_OTHER);

        verify(mMockAudioService,
                timeout(MAX_MESSAGE_HANDLING_DELAY_MS).atLeast(1)).onUpdatedAdiDeviceState(
                ArgumentMatchers.argThat(devState -> devState.getAudioDeviceCategory()
                        == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER));
    }

    private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
            boolean mockMediaPlayback, boolean guaranteeSingleConnection) throws Exception {
        assertNotNull("invalid null BT device", mFakeBtDevice);
        when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
                .thenReturn(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
        when(mMockAudioService.isInCommunication()).thenReturn(false);