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

Commit d4e4033c authored by Grzegorz Kolodziejczyk's avatar Grzegorz Kolodziejczyk Committed by Automerger Merge Worker
Browse files

Merge "a2dp: Notify about active device change when A2DP audio device is...

Merge "a2dp: Notify about active device change when A2DP audio device is added" into main am: 1368c5a5

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/2733933



Change-Id: Ia27d6b4d9ea2dd378db3a2c1e6f9b0761681016a
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 34cd62d0 1368c5a5
Loading
Loading
Loading
Loading
+102 −17
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
import android.os.Binder;
@@ -99,6 +101,8 @@ public class A2dpService extends ProfileService {

    @GuardedBy("mStateMachines")
    private BluetoothDevice mActiveDevice;

    private BluetoothDevice mExposedActiveDevice;
    private final ConcurrentMap<BluetoothDevice, A2dpStateMachine> mStateMachines =
            new ConcurrentHashMap<>();

@@ -116,6 +120,8 @@ public class A2dpService extends ProfileService {
    boolean mA2dpOffloadEnabled = false;

    private BroadcastReceiver mBondStateChangedReceiver;
    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback =
            new AudioManagerAudioDeviceCallback();

    A2dpService() {
        mNativeInterface = requireNonNull(A2dpNativeInterface.getInstance());
@@ -196,10 +202,13 @@ public class A2dpService extends ProfileService {
        mBondStateChangedReceiver = new BondStateChangedReceiver();
        registerReceiver(mBondStateChangedReceiver, filter);

        // Step 8: Mark service as started
        // Step 8: Register Audio Device callback
        mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler);

        // Step 9: Mark service as started
        setA2dpService(this);

        // Step 9: Clear active device
        // Step 10: Clear active device
        removeActiveDevice(false);

        return true;
@@ -213,12 +222,15 @@ public class A2dpService extends ProfileService {
            return true;
        }

        // Step 9: Clear active device and stop playing audio
        // Step 10: Clear active device and stop playing audio
        removeActiveDevice(true);

        // Step 8: Mark service as stopped
        // Step 9: Mark service as stopped
        setA2dpService(null);

        // Step 8: Unregister Audio Device Callback
        mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback);

        // Step 7: Unregister broadcast receivers
        unregisterReceiver(mBondStateChangedReceiver);
        mBondStateChangedReceiver = null;
@@ -506,10 +518,8 @@ public class A2dpService extends ProfileService {
            synchronized (mStateMachines) {
                if (mActiveDevice == null) return true;
                previousActiveDevice = mActiveDevice;
                mActiveDevice = null;
            }

            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
            updateAndBroadcastActiveDevice(null);

            // Make sure the Audio Manager knows the previous active device is no longer active.
@@ -590,15 +600,14 @@ public class A2dpService extends ProfileService {
                    return false;
                }
                previousActiveDevice = mActiveDevice;
                mActiveDevice = device;
            }

            // Switch from one A2DP to another A2DP device
            if (DBG) {
                Log.d(TAG, "Switch A2DP devices to " + device + " from " + previousActiveDevice);
            }
            // This needs to happen before we inform the audio manager that the device
            // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
            updateAndBroadcastActiveDevice(device);

            updateLowLatencyAudioSupport(device);

            BluetoothDevice newActiveDevice = null;
@@ -1052,10 +1061,89 @@ public class A2dpService extends ProfileService {
        }
    }

    // This needs to run before any of the Audio Manager connection functions since
    // AVRCP needs to be aware that the audio device is changed before the Audio Manager
    // changes the volume of the output devices.
    private void updateAndBroadcastActiveDevice(BluetoothDevice device) {
    /* Notifications of audio device connection/disconn events. */
    private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback {
        @Override
        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
            if (mAudioManager == null || mAdapterService == null) {
                Log.e(TAG, "Callback called when A2dpService is stopped");
                return;
            }

            synchronized (mStateMachines) {
                for (AudioDeviceInfo deviceInfo : addedDevices) {
                    if (deviceInfo.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
                        continue;
                    }

                    String address = deviceInfo.getAddress();
                    if (address.equals("00:00:00:00:00:00")) {
                        continue;
                    }

                    byte[] addressBytes = Utils.getBytesFromAddress(address);
                    BluetoothDevice device = mAdapterService.getDeviceFromByte(addressBytes);

                    if (DBG) {
                        Log.d(TAG, " onAudioDevicesAdded: " + device + ", device type: "
                                + deviceInfo.getType());
                    }

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


                    if (!device.equals(mActiveDevice)) {
                        Log.e(TAG, "Added device does not match to the one activated here. ("
                                + device + " != " + mActiveDevice
                                + " / " + mActiveDevice+ ")");
                        continue;
                    }

                    mExposedActiveDevice = device;
                    updateAndBroadcastActiveDevice(device);
                    return;
                }
            }
        }

        @Override
        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
            if (mAudioManager == null || mAdapterService == null) {
                Log.e(TAG, "Callback called when LeAudioService is stopped");
                return;
            }

            synchronized (mStateMachines) {
                for (AudioDeviceInfo deviceInfo : removedDevices) {
                    if (deviceInfo.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
                        continue;
                    }

                    String address = deviceInfo.getAddress();
                    if (address.equals("00:00:00:00:00:00")) {
                        continue;
                    }

                    mExposedActiveDevice = null;

                    if (DBG) {
                        Log.d(TAG, " onAudioDevicesRemoved: " + address + ", device type: "
                                + deviceInfo.getType()
                                + ", mActiveDevice: " + mActiveDevice);
                    }
                }
            }
        }
    }

    @VisibleForTesting
    void updateAndBroadcastActiveDevice(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "updateAndBroadcastActiveDevice(" + device + ")");
        }
@@ -1064,9 +1152,6 @@ public class A2dpService extends ProfileService {
        if (mFactory.getAvrcpTargetService() != null) {
            mFactory.getAvrcpTargetService().handleA2dpActiveDeviceChanged(device);
        }
        synchronized (mStateMachines) {
            mActiveDevice = device;
        }

        mAdapterService
                .getActiveDeviceManager()
+36 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ public class A2dpServiceTest {
    private final BlockingQueue<Intent> mConnectionStateChangedQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<Intent> mAudioStateChangedQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<Intent> mCodecConfigChangedQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<Intent> mActiveDeviceQueue = new LinkedBlockingQueue<>();

    @Mock private AdapterService mAdapterService;
    @Mock private ActiveDeviceManager mActiveDeviceManager;
@@ -117,6 +118,7 @@ public class A2dpServiceTest {
        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
        filter.addAction(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED);
        filter.addAction(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED);
        mA2dpIntentReceiver = new A2dpIntentReceiver();
        mTargetContext.registerReceiver(mA2dpIntentReceiver, filter);

@@ -135,6 +137,7 @@ public class A2dpServiceTest {
        mConnectionStateChangedQueue.clear();
        mAudioStateChangedQueue.clear();
        mCodecConfigChangedQueue.clear();
        mActiveDeviceQueue.clear();
        TestUtils.clearAdapterService(mAdapterService);
    }

@@ -163,6 +166,13 @@ public class A2dpServiceTest {
                    Assert.fail("Cannot add Intent to the Codec Config queue: " + e.getMessage());
                }
            }
            if (BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
                try {
                    mActiveDeviceQueue.put(intent);
                } catch (InterruptedException e) {
                    Assert.fail("Cannot add Intent to the device queue: " + e.getMessage());
                }
            }
        }
    }

@@ -214,6 +224,13 @@ public class A2dpServiceTest {
        Assert.assertNull(intent);
    }

    private void veifyActiveDeviceIntent(int timeoutMs, BluetoothDevice device) {
        Intent intent = TestUtils.waitForIntent(timeoutMs, mActiveDeviceQueue);
        Assert.assertNotNull(intent);
        Assert.assertEquals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, intent.getAction());
        Assert.assertEquals(device, intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
    }

    @Test
    public void testGetA2dpService() {
        Assert.assertEquals(mA2dpService, A2dpService.getA2dpService());
@@ -940,6 +957,25 @@ public class A2dpServiceTest {
        connectDeviceWithCodecStatus(device, null);
    }

    @Test
    public void testActiveDevice() {
        connectDevice(mTestDevice);

        /* Trigger setting active device */
        doReturn(true).when(mMockNativeInterface).setActiveDevice(any(BluetoothDevice.class));
        Assert.assertTrue(mA2dpService.setActiveDevice(mTestDevice));

        /* Check if setting active devices sets right device */
        Assert.assertEquals(mTestDevice, mA2dpService.getActiveDevice());

        /* Since A2dpService called AudioManager - assume Audio manager calles properly callback
         * mAudioManager.onAudioDeviceAdded
         */
        mA2dpService.updateAndBroadcastActiveDevice(mTestDevice);

        veifyActiveDeviceIntent(TIMEOUT_MS, mTestDevice);
    }

    private void connectDeviceWithCodecStatus(BluetoothDevice device,
            BluetoothCodecStatus codecStatus) {
        A2dpStackEvent connCompletedEvent;