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

Commit 27baad7e authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

LeAudioService: Add unit tests and minor fixes

Minor refactor to be able to extend unit tests.
Minor fix around activating LeAudio Source only device.

Bug: 331775328
Test: atest LeAudioServiceTest
Flag: Exempt, minor fix not affecting current use cases
Change-Id: Ic3ac66c0de4292fee52e8bfc5918e07e4085cf86
parent e4efe7b2
Loading
Loading
Loading
Loading
+75 −37
Original line number Diff line number Diff line
@@ -1741,6 +1741,70 @@ public class LeAudioService extends ProfileService {
        }
    }

    @VisibleForTesting
    boolean handleAudioDeviceAdded(
            BluetoothDevice device, int type, boolean isSink, boolean isSource) {
        Log.d(
                TAG,
                (" handleAudioDeviceAdded: " + device)
                        + (", device type: " + type)
                        + (", isSink: " + isSink)
                        + (" isSource: " + isSource));

        /* Don't expose already exposed active device */
        if (device.equals(mExposedActiveDevice)) {
            Log.d(TAG, " onAudioDevicesAdded: " + device + " is already exposed");
            return true;
        }

        if ((isSink && !device.equals(mActiveAudioOutDevice))
                || (isSource && !device.equals(mActiveAudioInDevice))) {
            Log.e(
                    TAG,
                    "Added device does not match to the one activated here. ("
                            + (device
                                    + " != "
                                    + mActiveAudioOutDevice
                                    + " / "
                                    + mActiveAudioInDevice
                                    + ")"));
            return false;
        }

        notifyActiveDeviceChanged(device);
        return true;
    }

    @VisibleForTesting
    void handleAudioDeviceRemoved(
            BluetoothDevice device, int type, boolean isSink, boolean isSource) {
        Log.d(
                TAG,
                (" handleAudioDeviceRemoved: " + device)
                        + (", device type: " + type)
                        + (", isSink: " + isSink)
                        + (" isSource: " + isSource)
                        + (", mActiveAudioInDevice: " + mActiveAudioInDevice)
                        + (", mActiveAudioOutDevice: " + mActiveAudioOutDevice));

        if (device != mExposedActiveDevice) {
            return;
        }

        if ((isSource && mActiveAudioInDevice == null)
                || (isSink && mActiveAudioOutDevice == null)) {
            Log.d(TAG, "Expecting device removal");
            if (mActiveAudioInDevice == null && mActiveAudioOutDevice == null) {
                mExposedActiveDevice = null;
            }
            return;
        }

        Log.i(TAG, "Audio manager disactivate LeAudio device " + mExposedActiveDevice);
        mExposedActiveDevice = null;
        setActiveDevice(null);
    }

    /* Notifications of audio device connection/disconn events. */
    private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
        @Override
@@ -1764,27 +1828,10 @@ public class LeAudioService extends ProfileService {
                byte[] addressBytes = Utils.getBytesFromAddress(address);
                BluetoothDevice device = mAdapterService.getDeviceFromByte(addressBytes);

                Log.d(TAG, " onAudioDevicesAdded: " + device + ", device type: "
                        + deviceInfo.getType() + ", isSink: " + deviceInfo.isSink()
                        + " isSource: " + deviceInfo.isSource());

                /* Don't expose already exposed active device */
                if (device.equals(mExposedActiveDevice)) {
                    Log.d(TAG, " onAudioDevicesAdded: " + device + " is already exposed");
                if (handleAudioDeviceAdded(
                        device, deviceInfo.getType(), deviceInfo.isSink(), deviceInfo.isSource())) {
                    return;
                }


                if ((deviceInfo.isSink() && !device.equals(mActiveAudioOutDevice))
                        || (deviceInfo.isSource() && !device.equals(mActiveAudioInDevice))) {
                    Log.e(TAG, "Added device does not match to the one activated here. ("
                            + device + " != " + mActiveAudioOutDevice
                            + " / " + mActiveAudioInDevice + ")");
                    continue;
                }

                notifyActiveDeviceChanged(device);
                return;
            }
        }

@@ -1809,19 +1856,8 @@ public class LeAudioService extends ProfileService {
                byte[] addressBytes = Utils.getBytesFromAddress(address);
                BluetoothDevice device = mAdapterService.getDeviceFromByte(addressBytes);

                Log.d(TAG, " onAudioDevicesRemoved: " + address + ", device type: "
                        + deviceInfo.getType() + ", isSink: " + deviceInfo.isSink()
                        + " isSource: " + deviceInfo.isSource()
                        + ", mActiveAudioInDevice: " + mActiveAudioInDevice
                        + ", mActiveAudioOutDevice: " +  mActiveAudioOutDevice);

                if (device != mExposedActiveDevice) {
                    continue;
                }

                Log.i(TAG, "Audio manager disactivate LeAudio device " + mExposedActiveDevice);
                mExposedActiveDevice = null;
                setActiveDevice(null);
                handleAudioDeviceRemoved(
                        device, deviceInfo.getType(), deviceInfo.isSink(), deviceInfo.isSource());
            }
        }
    }
@@ -1950,7 +1986,7 @@ public class LeAudioService extends ProfileService {
            notifyActiveDeviceChanged(null);
        }

        return mActiveAudioOutDevice != null;
        return mActiveAudioOutDevice != null || mActiveAudioInDevice != null;
    }

    private void clearInactiveDueToContextTypeFlags() {
@@ -2666,7 +2702,9 @@ public class LeAudioService extends ProfileService {
            return;
        }

        Log.d(TAG, "Transitionaing to Unicast stream for group: "
        Log.d(
                TAG,
                "Transitioning to Unicast stream for group: "
                        + mUnicastGroupIdDeactivatedForBroadcastTransition
                        + ", with device: "
                        + unicastDevice);
+123 −19
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.AudioDeviceInfo;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Handler;
import android.os.Looper;
@@ -105,6 +106,7 @@ public class LeAudioServiceTest {

    private BluetoothAdapter mAdapter;
    private Context mTargetContext;

    private LeAudioService mService;
    private BluetoothDevice mLeftDevice;
    private BluetoothDevice mRightDevice;
@@ -543,6 +545,36 @@ public class LeAudioServiceTest {
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }

    private void injectAudioDeviceAdded(
            BluetoothDevice device,
            int type,
            boolean isSink,
            boolean isSource,
            boolean expectedIntent) {
        mService.handleAudioDeviceAdded(device, type, isSink, isSource);
        if (expectedIntent) {
            verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, device);
        } else {
            Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(device));
            assertThat(intent).isNull();
        }
    }

    private void injectAudioDeviceRemoved(
            BluetoothDevice device,
            int type,
            boolean isSink,
            boolean isSource,
            boolean expectedIntent) {
        mService.handleAudioDeviceRemoved(device, type, isSink, isSource);
        if (expectedIntent) {
            verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, null);
        } else {
            Intent intent = TestUtils.waitForNoIntent(TIMEOUT_MS, mDeviceQueueMap.get(device));
            assertThat(intent).isNull();
        }
    }

    private void injectNoVerifyDeviceConnected(BluetoothDevice device) {
        generateUnexpectedConnectionMessageFromNative(device,
                LeAudioStackEvent.CONNECTION_STATE_CONNECTED,
@@ -1339,7 +1371,7 @@ public class LeAudioServiceTest {
        /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback
         * mAudioManager.onAudioDeviceAdded
         */
        mService.notifyActiveDeviceChanged(mSingleDevice);
        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);

        reset(mNativeInterface);

@@ -1453,8 +1485,7 @@ public class LeAudioServiceTest {
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), eq(null), connectionInfoArgumentCaptor.capture());

        mService.notifyActiveDeviceChanged(mSingleDevice);
        verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, mSingleDevice);
        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);

        BluetoothProfileConnectionInfo connInfo = connectionInfoArgumentCaptor.getValue();
        assertThat(connInfo.isSuppressNoisyIntent()).isTrue();
@@ -1478,7 +1509,8 @@ public class LeAudioServiceTest {
                        eq(null), eq(mSingleDevice), connectionInfoArgumentCaptor.capture());
        connInfo = connectionInfoArgumentCaptor.getValue();
        assertThat(connInfo.isSuppressNoisyIntent()).isTrue();
        mService.notifyActiveDeviceChanged(null);
        injectAudioDeviceRemoved(
                mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, false);

        reset(mAudioManager);

@@ -1495,7 +1527,8 @@ public class LeAudioServiceTest {
        connInfo = connectionInfoArgumentCaptor.getValue();
        assertThat(connInfo.isSuppressNoisyIntent()).isTrue();

        mService.notifyActiveDeviceChanged(mSingleDevice_2);
        injectAudioDeviceAdded(
                mSingleDevice_2, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
    }

    /**
@@ -1686,6 +1719,86 @@ public class LeAudioServiceTest {
        audioConfChangedEvent.valueInt5 = availableContexts;
        mService.messageFromNative(audioConfChangedEvent);
    }

    /** Test group direction changed */
    @Test
    public void testGroupDirectionChanged_AudioConfChangedActiveGroup() {

        int testVolume = 100;

        ArgumentCaptor<BluetoothProfileConnectionInfo> testConnectioInfoCapture =
                ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);

        doReturn(testVolume).when(mVolumeControlService).getAudioDeviceGroupVolume(testGroupId);

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);
        injectAudioConfChanged(
                testGroupId,
                BluetoothLeAudio.CONTEXT_TYPE_MEDIA | BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL,
                3);
        injectGroupStatusChange(testGroupId, BluetoothLeAudio.GROUP_STATUS_ACTIVE);

        verify(mAudioManager, times(2))
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), eq(null), testConnectioInfoCapture.capture());

        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, false, true, false);

        reset(mAudioManager);
        /* Verify input and output has been connected to AF*/
        List<BluetoothProfileConnectionInfo> connInfos = testConnectioInfoCapture.getAllValues();
        assertThat(connInfos.size()).isEqualTo(2);
        assertThat(connInfos.get(0).isLeOutput()).isEqualTo(true);
        assertThat(connInfos.get(1).isLeOutput()).isEqualTo(false);

        // Remove source direction
        injectAudioConfChanged(
                testGroupId,
                BluetoothLeAudio.CONTEXT_TYPE_MEDIA | BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL,
                1);

        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mSingleDevice), testConnectioInfoCapture.capture());

        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, false);
        injectAudioDeviceRemoved(
                mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, false, true, false);

        reset(mAudioManager);

        connInfos = testConnectioInfoCapture.getAllValues();
        assertThat(connInfos.size()).isEqualTo(3);
        assertThat(connInfos.get(2).isLeOutput()).isEqualTo(false);

        // remove Sink and add Source back

        injectAudioConfChanged(
                testGroupId,
                BluetoothLeAudio.CONTEXT_TYPE_MEDIA | BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL,
                2);

        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), eq(mSingleDevice), testConnectioInfoCapture.capture());
        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(mSingleDevice), eq(null), testConnectioInfoCapture.capture());

        injectAudioDeviceRemoved(
                mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, false);
        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, false, true, false);

        reset(mAudioManager);

        connInfos = testConnectioInfoCapture.getAllValues();
        assertThat(connInfos.size()).isEqualTo(5);
        assertThat(connInfos.get(3).isLeOutput()).isEqualTo(true);
        assertThat(connInfos.get(4).isLeOutput()).isEqualTo(false);
    }

    /**
     * Test native interface audio configuration changed message handling
     */
@@ -1704,14 +1817,7 @@ public class LeAudioServiceTest {
        /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback
         * mAudioManager.onAudioDeviceAdded
         */
        mService.notifyActiveDeviceChanged(mSingleDevice);

        String action = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
        Intent intent = TestUtils.waitForIntent(TIMEOUT_MS, mDeviceQueueMap.get(mSingleDevice));
        assertThat(intent).isNotNull();
        assertThat(action).isEqualTo(intent.getAction());
        assertThat(mSingleDevice)
                .isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
        injectAudioDeviceAdded(mSingleDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
    }
    /**
     * Test native interface audio configuration changed message handling
@@ -2168,9 +2274,8 @@ public class LeAudioServiceTest {
        /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback
         * mAudioManager.onAudioDeviceAdded
         */
        mService.notifyActiveDeviceChanged(leadDevice);
        injectAudioDeviceAdded(leadDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);
        doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService).getBondState(leadDevice);
        verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, leadDevice);
        injectNoVerifyDeviceDisconnected(leadDevice);

        // We should not change the audio device
@@ -2241,9 +2346,8 @@ public class LeAudioServiceTest {
        /* Since LeAudioService called AudioManager - assume Audio manager calles properly callback
         * mAudioManager.onAudioDeviceAdded
         */
        mService.notifyActiveDeviceChanged(leadDevice);
        injectAudioDeviceAdded(leadDevice, AudioDeviceInfo.TYPE_BLE_HEADSET, true, false, true);

        verifyActiveDeviceStateIntent(AUDIO_MANAGER_DEVICE_ADD_TIMEOUT_MS, leadDevice);
        /* We don't want to distribute DISCONNECTION event, instead will try to reconnect
         * (in native)
         */