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

Commit 9b5def86 authored by Grzegorz Kołodziejczyk's avatar Grzegorz Kołodziejczyk
Browse files

le_audio: Implement handover to broadcast for Telecomm call and record

This CL adds autonomous switch to/from broadcast/unicast capability,
driven by TBS (Telecomm) API handled by LeAudioService and monitoring
Sink Bluetooth HAL session for e.g. recorder applications.

Tag: #feature
Test: atest LeAudioBroadcastServiceTest
Bug: 298079825
Bug: 308171251
Change-Id: Idf1806c2027d386b77f1e1076c766efd2f4fd7ae
parent c2f9bbd2
Loading
Loading
Loading
Loading
+243 −43
Original line number Original line Diff line number Diff line
@@ -48,6 +48,7 @@ import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.bluetooth.le.ScanSettings;
import android.content.AttributionSource;
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioDeviceInfo;
@@ -89,6 +90,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Collectors;


/**
/**
@@ -129,7 +131,7 @@ public class LeAudioService extends ProfileService {
    private BluetoothDevice mExposedActiveDevice;
    private BluetoothDevice mExposedActiveDevice;
    private LeAudioCodecConfig mLeAudioCodecConfig;
    private LeAudioCodecConfig mLeAudioCodecConfig;
    private final Object mGroupLock = new Object();
    private final Object mGroupLock = new Object();
    private FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
    private final FeatureFlags mFeatureFlags;
    ServiceFactory mServiceFactory = new ServiceFactory();
    ServiceFactory mServiceFactory = new ServiceFactory();


    LeAudioNativeInterface mLeAudioNativeInterface;
    LeAudioNativeInterface mLeAudioNativeInterface;
@@ -143,6 +145,8 @@ public class LeAudioService extends ProfileService {
    LeAudioTmapGattServer mTmapGattServer;
    LeAudioTmapGattServer mTmapGattServer;
    int mTmapRoleMask;
    int mTmapRoleMask;
    int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID;
    int mUnicastGroupIdDeactivatedForBroadcastTransition = LE_AUDIO_GROUP_ID_INVALID;
    Optional<Integer> mBroadcastIdDeactivatedForUnicastTransition = Optional.empty();
    Optional<Boolean> mQueuedInCallValue = Optional.empty();
    boolean mTmapStarted = false;
    boolean mTmapStarted = false;
    private boolean mAwaitingBroadcastCreateResponse = false;
    private boolean mAwaitingBroadcastCreateResponse = false;
    private final LinkedList<BluetoothLeBroadcastSettings> mCreateBroadcastQueue =
    private final LinkedList<BluetoothLeBroadcastSettings> mCreateBroadcastQueue =
@@ -248,6 +252,17 @@ public class LeAudioService extends ProfileService {
    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback =
    private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback =
            new AudioManagerAudioDeviceCallback();
            new AudioManagerAudioDeviceCallback();


    LeAudioService() {
        mFeatureFlags = new FeatureFlagsImpl();
    }

    @VisibleForTesting
    LeAudioService(Context ctx, FeatureFlags featureFlags) {
        attachBaseContext(ctx);
        mFeatureFlags = featureFlags;
        onCreate();
    }

    @Override
    @Override
    protected IProfileServiceBinder initBinder() {
    protected IProfileServiceBinder initBinder() {
        return new BluetoothLeAudioBinder(this);
        return new BluetoothLeAudioBinder(this);
@@ -379,6 +394,7 @@ public class LeAudioService extends ProfileService {
            return true;
            return true;
        }
        }


        mQueuedInCallValue = Optional.empty();
        mCreateBroadcastQueue.clear();
        mCreateBroadcastQueue.clear();
        mAwaitingBroadcastCreateResponse = false;
        mAwaitingBroadcastCreateResponse = false;


@@ -405,7 +421,7 @@ public class LeAudioService extends ProfileService {
                if (descriptor.mIsActive) {
                if (descriptor.mIsActive) {
                    descriptor.mIsActive = false;
                    descriptor.mIsActive = false;
                    updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE,
                    updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE,
                            descriptor.mIsActive, false);
                            descriptor.mIsActive, false, false);
                    break;
                    break;
                }
                }
            }
            }
@@ -502,11 +518,6 @@ public class LeAudioService extends ProfileService {
        sLeAudioService = instance;
        sLeAudioService = instance;
    }
    }


    @VisibleForTesting
    void setFeatureFlags(FeatureFlags featureFlags) {
        mFeatureFlags = featureFlags;
    }

    VolumeControlService getVolumeControlService() {
    VolumeControlService getVolumeControlService() {
        if (mVolumeControlService == null) {
        if (mVolumeControlService == null) {
            mVolumeControlService = mServiceFactory.getVolumeControlService();
            mVolumeControlService = mServiceFactory.getVolumeControlService();
@@ -859,6 +870,10 @@ public class LeAudioService extends ProfileService {
            Log.i(TAG, "Unicast group is active, queueing Broadcast creation, while the Unicast"
            Log.i(TAG, "Unicast group is active, queueing Broadcast creation, while the Unicast"
                        + " group is deactivated.");
                        + " group is deactivated.");
            mCreateBroadcastQueue.add(broadcastSettings);
            mCreateBroadcastQueue.add(broadcastSettings);
            if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) {
                mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK,
                        true);
            }
            removeActiveDevice(true);
            removeActiveDevice(true);


            return;
            return;
@@ -948,6 +963,27 @@ public class LeAudioService extends ProfileService {
                        .toArray(byte[][]::new));
                        .toArray(byte[][]::new));
    }
    }


    /**
     * Pause LeAudio Broadcast instance.
     *
     * @param broadcastId broadcast instance identifier
     */
    public void pauseBroadcast(Integer broadcastId) {
        if (mLeAudioBroadcasterNativeInterface == null) {
            Log.w(TAG, "Native interface not available.");
            return;
        }

        LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId);
        if (descriptor == null) {
            Log.e(TAG, "pauseBroadcast: No valid descriptor for broadcastId: " + broadcastId);
            return;
        }

        if (DBG) Log.d(TAG, "pauseBroadcast");
        mLeAudioBroadcasterNativeInterface.pauseBroadcast(broadcastId);
    }

    /**
    /**
     * Stop LeAudio Broadcast instance.
     * Stop LeAudio Broadcast instance.
     * @param broadcastId broadcast instance identifier
     * @param broadcastId broadcast instance identifier
@@ -989,6 +1025,9 @@ public class LeAudioService extends ProfileService {
        }
        }


        if (DBG) Log.d(TAG, "destroyBroadcast");
        if (DBG) Log.d(TAG, "destroyBroadcast");
        if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()) {
            mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK, false);
        }
        mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId);
        mLeAudioBroadcasterNativeInterface.destroyBroadcast(broadcastId);
    }
    }


@@ -1046,6 +1085,32 @@ public class LeAudioService extends ProfileService {
        return 1;
        return 1;
    }
    }


    private boolean areBroadcastsAllStopped() {
        if (mBroadcastDescriptors == null) {
            Log.e(TAG, "areBroadcastsAllStopped: Invalid Broadcast Descriptors");
            return false;
        }

        return mBroadcastDescriptors.values().stream()
                .allMatch(d -> d.mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED));
    }

    private Optional<Integer> getFirstNotStoppedBroadcastId() {
        if (mBroadcastDescriptors == null) {
            Log.e(TAG, "getFirstNotStoppedBroadcastId: Invalid Broadcast Descriptors");
            return Optional.empty();
        }

        for (Map.Entry<Integer, LeAudioBroadcastDescriptor> entry :
                mBroadcastDescriptors.entrySet()) {
            if (!entry.getValue().mState.equals(LeAudioStackEvent.BROADCAST_STATE_STOPPED)) {
                return Optional.of(entry.getKey());
            }
        }

        return Optional.empty();
    }

    private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) {
    private BluetoothDevice getLeadDeviceForTheGroup(Integer groupId) {
        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
        if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
            return null;
            return null;
@@ -1453,35 +1518,80 @@ public class LeAudioService extends ProfileService {
        }
        }
    }
    }


    /*
     * Report the active broadcast device change to the active device manager and the media
     * framework.
     * @param newDevice new supported broadcast audio device
     * @param previousDevice previous no longer supported broadcast audio device
     */
    private void updateBroadcastActiveDevice(
            BluetoothDevice newDevice, BluetoothDevice previousDevice) {
        mActiveAudioOutDevice = newDevice;
        mAudioManager.handleBluetoothActiveDeviceChanged(
                newDevice, previousDevice, getBroadcastProfile(true));
    }

    /*
     * Listen mode is set when broadcast is queued, waiting for create response notification or
     * descriptor was created - idicate that create notification was received.
     */
    private boolean wasSetSinkListeningMode() {
        return !mCreateBroadcastQueue.isEmpty() || mAwaitingBroadcastCreateResponse
                || !mBroadcastDescriptors.isEmpty();
    }

    /**
    /**
     * Report the active devices change to the active device manager and the media framework.
     * Report the active devices change to the active device manager and the media framework.
     *
     * @param groupId id of group which devices should be updated
     * @param groupId id of group which devices should be updated
     * @param newSupportedAudioDirections new supported audio directions for group of devices
     * @param newSupportedAudioDirections new supported audio directions for group of devices
     * @param oldSupportedAudioDirections old supported audio directions for group of devices
     * @param oldSupportedAudioDirections old supported audio directions for group of devices
     * @param isActive if there is new active group
     * @param isActive if there is new active group
     * @param hasFallbackDevice whether any fallback device exists when deactivating
     * @param hasFallbackDevice whether any fallback device exists when deactivating the current
     *                          the current active device.
     *     active device.
     * @param notifyAndUpdateInactiveOutDeviceOnly if only output device should be updated to
     *     inactive devices (if new out device would be null device).
     * @return true if group is active after change false otherwise.
     * @return true if group is active after change false otherwise.
     */
     */
    private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections,
    private boolean updateActiveDevices(
            Integer newSupportedAudioDirections, boolean isActive, boolean hasFallbackDevice) {
            Integer groupId,
        BluetoothDevice device = null;
            Integer oldSupportedAudioDirections,
            Integer newSupportedAudioDirections,
            boolean isActive,
            boolean hasFallbackDevice,
            boolean notifyAndUpdateInactiveOutDeviceOnly) {
        BluetoothDevice newOutDevice = null;
        BluetoothDevice newInDevice = null;
        BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice;
        BluetoothDevice previousActiveOutDevice = mActiveAudioOutDevice;
        BluetoothDevice previousActiveInDevice = mActiveAudioInDevice;
        BluetoothDevice previousActiveInDevice = mActiveAudioInDevice;


        if (isActive) {
        if (isActive) {
            device = getLeadDeviceForTheGroup(groupId);
            newOutDevice = getLeadDeviceForTheGroup(groupId);
            newInDevice = newOutDevice;
        } else {
            /* While broadcasting a input device needs to be connected to track Audio Framework
             * streaming requests. This would allow native to make a fallback to Unicast decision.
             */
            if (notifyAndUpdateInactiveOutDeviceOnly
                    && ((newSupportedAudioDirections & AUDIO_DIRECTION_INPUT_BIT) != 0)) {
                newInDevice = getLeadDeviceForTheGroup(groupId);
            } else if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()
                    && wasSetSinkListeningMode()) {
                mLeAudioNativeInterface.setUnicastMonitorMode(LeAudioStackEvent.DIRECTION_SINK,
                        false);
            }
        }
        }


        boolean isNewActiveOutDevice = updateActiveOutDevice(device, groupId,
        boolean isNewActiveOutDevice = updateActiveOutDevice(newOutDevice, groupId,
                oldSupportedAudioDirections, newSupportedAudioDirections);
                oldSupportedAudioDirections, newSupportedAudioDirections);
        boolean isNewActiveInDevice = updateActiveInDevice(device, groupId,
        boolean isNewActiveInDevice = updateActiveInDevice(newInDevice, groupId,
                oldSupportedAudioDirections, newSupportedAudioDirections);
                oldSupportedAudioDirections, newSupportedAudioDirections);


        if (DBG) {
        if (DBG) {
            Log.d(TAG, " isNewActiveOutDevice: " + isNewActiveOutDevice + ", "
            Log.d(TAG, " isNewActiveOutDevice: " + isNewActiveOutDevice + ", "
                    + mActiveAudioOutDevice + ", isNewActiveInDevice: " + isNewActiveInDevice
                    + mActiveAudioOutDevice + ", isNewActiveInDevice: " + isNewActiveInDevice
                    + ", " + mActiveAudioInDevice);
                    + ", " + mActiveAudioInDevice + ", notifyAndUpdateInactiveOutDeviceOnly: "
                    + notifyAndUpdateInactiveOutDeviceOnly);
        }
        }


        if (isNewActiveOutDevice) {
        if (isNewActiveOutDevice) {
@@ -1514,11 +1624,12 @@ public class LeAudioService extends ProfileService {


        if (isNewActiveInDevice) {
        if (isNewActiveInDevice) {
            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice,
            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice,
                    previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo(false,
                    previousActiveInDevice, BluetoothProfileConnectionInfo.createLeAudioInfo(
                            false));
                            false, false));
        }
        }


        if ((mActiveAudioOutDevice == null) && (mActiveAudioInDevice == null)) {
        if ((mActiveAudioOutDevice == null)
                && (notifyAndUpdateInactiveOutDeviceOnly || (mActiveAudioInDevice == null))) {
            /* Notify about inactive device as soon as possible.
            /* Notify about inactive device as soon as possible.
             * When adding new device, wait with notification until AudioManager is ready
             * When adding new device, wait with notification until AudioManager is ready
             * with adding the device.
             * with adding the device.
@@ -1882,7 +1993,7 @@ public class LeAudioService extends ProfileService {
            }
            }


            descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE,
            descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE,
                    descriptor.mDirection, true, false);
                    descriptor.mDirection, true, false, false);


            if (descriptor.mIsActive) {
            if (descriptor.mIsActive) {
                notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
                notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
@@ -1900,13 +2011,26 @@ public class LeAudioService extends ProfileService {
                return;
                return;
            }
            }


            /* Group became inactive due to broadcast creation, check if input device should remain
             * connected to track streaming request on Unicast
             */
            boolean leaveConnectedInputDevice = false;
            Integer newDirections = AUDIO_DIRECTION_NONE;
            if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies()
                    && (!mCreateBroadcastQueue.isEmpty()
                            || mBroadcastIdDeactivatedForUnicastTransition.isPresent())) {
                leaveConnectedInputDevice = true;
                newDirections |= AUDIO_DIRECTION_INPUT_BIT;
            }

            descriptor.mIsActive = false;
            descriptor.mIsActive = false;
            updateActiveDevices(
            updateActiveDevices(
                    groupId,
                    groupId,
                    descriptor.mDirection,
                    descriptor.mDirection,
                    AUDIO_DIRECTION_NONE,
                    newDirections,
                    descriptor.mIsActive,
                    descriptor.mIsActive,
                    descriptor.mHasFallbackDeviceWhenGettingInactive);
                    descriptor.mHasFallbackDeviceWhenGettingInactive,
                    leaveConnectedInputDevice);
            /* Clear lost devices */
            /* Clear lost devices */
            if (DBG) Log.d(TAG, "Clear for group: " + groupId);
            if (DBG) Log.d(TAG, "Clear for group: " + groupId);
            descriptor.mHasFallbackDeviceWhenGettingInactive = false;
            descriptor.mHasFallbackDeviceWhenGettingInactive = false;
@@ -1916,6 +2040,32 @@ public class LeAudioService extends ProfileService {
        }
        }
    }
    }


    private void handleUnicastStreamStatusChange(int status) {
        if (DBG) {
            Log.d(TAG, "status: " + status);
        }

        /* Straming request of Unicast Sink stream should result in pausing broadcast and activating
         * Unicast group.
         *
         * When stream is suspended there should be a reverse handover. Active Unicast group should
         * become inactive and broadcast should be resumed grom paused state.
         */
        if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED) {
            Optional<Integer> broadcastId = getFirstNotStoppedBroadcastId();
            if (broadcastId.isEmpty() || (mBroadcastDescriptors.get(broadcastId.get()) == null)) {
                Log.e(TAG, "handleUnicastStreamStatusChange: Broadcast to Unicast handover not"
                        + " possible");
                return;
            }

            mBroadcastIdDeactivatedForUnicastTransition = Optional.of(broadcastId.get());
            pauseBroadcast(broadcastId.get());
        } else if (status == LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED) {
            removeActiveDevice(true);
        }
    }

    @VisibleForTesting
    @VisibleForTesting
    void handleGroupIdleDuringCall() {
    void handleGroupIdleDuringCall() {
        if (mHfpHandoverDevice == null) {
        if (mHfpHandoverDevice == null) {
@@ -2091,6 +2241,16 @@ public class LeAudioService extends ProfileService {
    }
    }


    void transitionFromBroadcastToUnicast() {
    void transitionFromBroadcastToUnicast() {
        if (mUnicastGroupIdDeactivatedForBroadcastTransition == LE_AUDIO_GROUP_ID_INVALID) {
            Log.d(TAG, "No deactivated group due for broadcast transmission");
            return;
        }

        if (mQueuedInCallValue.isPresent()) {
            mLeAudioNativeInterface.setInCall(mQueuedInCallValue.get());
            mQueuedInCallValue = Optional.empty();
        }

        BluetoothDevice unicastDevice =
        BluetoothDevice unicastDevice =
                getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition);
                getLeadDeviceForTheGroup(mUnicastGroupIdDeactivatedForBroadcastTransition);
        if (unicastDevice == null) {
        if (unicastDevice == null) {
@@ -2272,7 +2432,7 @@ public class LeAudioService extends ProfileService {
                    if (descriptor.mIsActive) {
                    if (descriptor.mIsActive) {
                        descriptor.mIsActive =
                        descriptor.mIsActive =
                                updateActiveDevices(groupId, descriptor.mDirection, direction,
                                updateActiveDevices(groupId, descriptor.mDirection, direction,
                                descriptor.mIsActive, false);
                                descriptor.mIsActive, false, false);
                        if (!descriptor.mIsActive) {
                        if (!descriptor.mIsActive) {
                            notifyGroupStatusChanged(groupId,
                            notifyGroupStatusChanged(groupId,
                                    BluetoothLeAudio.GROUP_STATUS_INACTIVE);
                                    BluetoothLeAudio.GROUP_STATUS_INACTIVE);
@@ -2323,6 +2483,15 @@ public class LeAudioService extends ProfileService {
                }
                }
                case LeAudioStackEvent.GROUP_STATUS_INACTIVE: {
                case LeAudioStackEvent.GROUP_STATUS_INACTIVE: {
                    handleGroupTransitToInactive(groupId);
                    handleGroupTransitToInactive(groupId);

                    /* Check if broadcast was deactivated due to unicast */
                    if (mBroadcastIdDeactivatedForUnicastTransition.isPresent()) {
                        mUnicastGroupIdDeactivatedForBroadcastTransition = groupId;
                        mQueuedInCallValue = Optional.empty();
                        startBroadcast(mBroadcastIdDeactivatedForUnicastTransition.get());
                        mBroadcastIdDeactivatedForUnicastTransition = Optional.empty();
                    }

                    if (!mCreateBroadcastQueue.isEmpty()) {
                    if (!mCreateBroadcastQueue.isEmpty()) {
                        mUnicastGroupIdDeactivatedForBroadcastTransition = groupId;
                        mUnicastGroupIdDeactivatedForBroadcastTransition = groupId;
                        BluetoothLeBroadcastSettings settings = mCreateBroadcastQueue.remove();
                        BluetoothLeBroadcastSettings settings = mCreateBroadcastQueue.remove();
@@ -2382,14 +2551,10 @@ public class LeAudioService extends ProfileService {
            }
            }
            mBroadcastDescriptors.remove(broadcastId);
            mBroadcastDescriptors.remove(broadcastId);


            /* Restore the Unicast stream from before the Broadcast was started. */
            if (mUnicastGroupIdDeactivatedForBroadcastTransition != LE_AUDIO_GROUP_ID_INVALID) {
                transitionFromBroadcastToUnicast();
            }

        } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE) {
        } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE) {
            int broadcastId = stackEvent.valueInt1;
            int broadcastId = stackEvent.valueInt1;
            int state = stackEvent.valueInt2;
            int state = stackEvent.valueInt2;
            int previousState;


            LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId);
            LeAudioBroadcastDescriptor descriptor = mBroadcastDescriptors.get(broadcastId);
            if (descriptor == null) {
            if (descriptor == null) {
@@ -2403,6 +2568,7 @@ public class LeAudioService extends ProfileService {
                mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId);
                mLeAudioBroadcasterNativeInterface.getBroadcastMetadata(broadcastId);
                descriptor.mRequestedForDetails = true;
                descriptor.mRequestedForDetails = true;
            }
            }
            previousState = descriptor.mState;
            descriptor.mState = state;
            descriptor.mState = state;


            switch (descriptor.mState) {
            switch (descriptor.mState) {
@@ -2419,15 +2585,14 @@ public class LeAudioService extends ProfileService {
                                    d ->
                                    d ->
                                            d.mState.equals(
                                            d.mState.equals(
                                                    LeAudioStackEvent.BROADCAST_STATE_STREAMING))) {
                                                    LeAudioStackEvent.BROADCAST_STATE_STREAMING))) {
                        if (Objects.equals(device, mActiveAudioOutDevice)) {
                        updateBroadcastActiveDevice(null, mActiveAudioOutDevice);
                            BluetoothDevice previousDevice = mActiveAudioOutDevice;
                            mActiveAudioOutDevice = null;
                            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
                                    previousDevice,
                                    getBroadcastProfile(true));
                        }
                    }
                    }


                    /* Restore the Unicast stream from before the Broadcast was started. */
                    if (mUnicastGroupIdDeactivatedForBroadcastTransition
                            != LE_AUDIO_GROUP_ID_INVALID) {
                        transitionFromBroadcastToUnicast();
                    }
                    destroyBroadcast(broadcastId);
                    destroyBroadcast(broadcastId);
                    break;
                    break;
                case LeAudioStackEvent.BROADCAST_STATE_CONFIGURING:
                case LeAudioStackEvent.BROADCAST_STATE_CONFIGURING:
@@ -2436,9 +2601,20 @@ public class LeAudioService extends ProfileService {
                case LeAudioStackEvent.BROADCAST_STATE_PAUSED:
                case LeAudioStackEvent.BROADCAST_STATE_PAUSED:
                    if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " paused.");
                    if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " paused.");


                    /* Stop here if Broadcast was not in Streaming state before */
                    if (previousState != LeAudioStackEvent.BROADCAST_STATE_STREAMING) {
                        return;
                    }

                    // Playback paused
                    // Playback paused
                    notifyPlaybackStopped(broadcastId,
                    notifyPlaybackStopped(broadcastId,
                            BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);
                            BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);

                    // Notify audio manager
                    updateBroadcastActiveDevice(null, mActiveAudioOutDevice);

                    /* Restore the Unicast stream from before the Broadcast was started. */
                    transitionFromBroadcastToUnicast();
                    break;
                    break;
                case LeAudioStackEvent.BROADCAST_STATE_STOPPING:
                case LeAudioStackEvent.BROADCAST_STATE_STOPPING:
                    if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping.");
                    if (DBG) Log.d(TAG, "Broadcast broadcastId: " + broadcastId + " stopping.");
@@ -2457,11 +2633,7 @@ public class LeAudioService extends ProfileService {
                                            d.mState.equals(
                                            d.mState.equals(
                                                    LeAudioStackEvent.BROADCAST_STATE_STREAMING))) {
                                                    LeAudioStackEvent.BROADCAST_STATE_STREAMING))) {
                        if (!Objects.equals(device, mActiveAudioOutDevice)) {
                        if (!Objects.equals(device, mActiveAudioOutDevice)) {
                            BluetoothDevice previousDevice = mActiveAudioOutDevice;
                            updateBroadcastActiveDevice(device, mActiveAudioOutDevice);
                            mActiveAudioOutDevice = device;
                            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
                                    previousDevice,
                                    getBroadcastProfile(false));
                        }
                        }
                    }
                    }
                    break;
                    break;
@@ -2494,6 +2666,12 @@ public class LeAudioService extends ProfileService {
            if (!mTmapStarted) {
            if (!mTmapStarted) {
                mTmapStarted = registerTmap();
                mTmapStarted = registerTmap();
            }
            }
        } else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_UNICAST_MONITOR_MODE_STATUS) {
            if (stackEvent.valueInt1 == LeAudioStackEvent.DIRECTION_SINK) {
                handleUnicastStreamStatusChange(stackEvent.valueInt2);
            } else {
                Log.e(TAG, "Invalid direction: " + stackEvent.valueInt2);
            }
        }
        }
    }
    }


@@ -2699,17 +2877,20 @@ public class LeAudioService extends ProfileService {
                            descriptor.mDirection,
                            descriptor.mDirection,
                            descriptor.mDirection,
                            descriptor.mDirection,
                            descriptor.mIsActive,
                            descriptor.mIsActive,
                            hasFallbackDevice);
                            hasFallbackDevice,
                            false);
                    return;
                    return;
                }
                }
            }
            }


            if (descriptor.mIsActive) {
            if (descriptor.mIsActive || Objects.equals(mActiveAudioOutDevice, device)
                    || Objects.equals(mActiveAudioInDevice, device)) {
                updateActiveDevices(deviceDescriptor.mGroupId,
                updateActiveDevices(deviceDescriptor.mGroupId,
                        descriptor.mDirection,
                        descriptor.mDirection,
                        descriptor.mDirection,
                        descriptor.mDirection,
                        descriptor.mIsActive,
                        descriptor.mIsActive,
                        hasFallbackDevice);
                        hasFallbackDevice,
                        false);
            }
            }
        }
        }
    }
    }
@@ -2791,7 +2972,24 @@ public class LeAudioService extends ProfileService {
            Log.e(TAG, "Le Audio not initialized properly.");
            Log.e(TAG, "Le Audio not initialized properly.");
            return;
            return;
        }
        }

        /* For setting inCall mode */
        if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && inCall
                && !areBroadcastsAllStopped()) {
            mQueuedInCallValue = Optional.of(true);

            /* Request activation of unicast group */
            handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_REQUESTED);
            return;
        }

        mLeAudioNativeInterface.setInCall(inCall);
        mLeAudioNativeInterface.setInCall(inCall);

        /* For clearing inCall mode */
        if (mFeatureFlags.leaudioBroadcastAudioHandoverPolicies() && !inCall
                && mBroadcastIdDeactivatedForUnicastTransition.isPresent()) {
            handleUnicastStreamStatusChange(LeAudioStackEvent.STATUS_LOCAL_STREAM_SUSPENDED);
        }
    }
    }


    /**
    /**
@@ -4179,6 +4377,8 @@ public class LeAudioService extends ProfileService {
        ProfileService.println(sb, "  mActiveAudioInDevice: " + mActiveAudioInDevice);
        ProfileService.println(sb, "  mActiveAudioInDevice: " + mActiveAudioInDevice);
        ProfileService.println(sb, "  mUnicastGroupIdDeactivatedForBroadcastTransition: "
        ProfileService.println(sb, "  mUnicastGroupIdDeactivatedForBroadcastTransition: "
                + mUnicastGroupIdDeactivatedForBroadcastTransition);
                + mUnicastGroupIdDeactivatedForBroadcastTransition);
        ProfileService.println(sb, "  mBroadcastIdDeactivatedForUnicastTransition: "
                + mBroadcastIdDeactivatedForUnicastTransition);
        ProfileService.println(sb, "  mExposedActiveDevice: " + mExposedActiveDevice);
        ProfileService.println(sb, "  mExposedActiveDevice: " + mExposedActiveDevice);
        ProfileService.println(sb, "  mHfpHandoverDevice:" + mHfpHandoverDevice);
        ProfileService.println(sb, "  mHfpHandoverDevice:" + mHfpHandoverDevice);
        ProfileService.println(sb, "  mLeAudioIsInbandRingtoneSupported:"
        ProfileService.println(sb, "  mLeAudioIsInbandRingtoneSupported:"
+269 −28

File changed.

Preview size limit exceeded, changes collapsed.

+11 −16
Original line number Original line Diff line number Diff line
@@ -194,8 +194,17 @@ public class LeAudioServiceTest {
        doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
        doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
                mAdapterService).getBondedDevices();
                mAdapterService).getBondedDevices();


        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT,
                false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION,
                false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_BROADCAST_AUDIO_HANDOVER_POLICIES, false);

        LeAudioNativeInterface.setInstance(mNativeInterface);
        LeAudioNativeInterface.setInstance(mNativeInterface);
        startService();
        mService = new LeAudioService(mTargetContext, mFakeFlagsImpl);
        mService.doStart();

        mService.mAudioManager = mAudioManager;
        mService.mAudioManager = mAudioManager;
        mService.mMcpService = mMcpService;
        mService.mMcpService = mMcpService;
        mService.mTbsService = mTbsService;
        mService.mTbsService = mTbsService;
@@ -256,7 +265,7 @@ public class LeAudioServiceTest {


        mBondedDevices.clear();
        mBondedDevices.clear();
        mGroupIntentQueue.clear();
        mGroupIntentQueue.clear();
        stopService();
        mService.doStop();
        if (mDeviceQueueMap != null) {
        if (mDeviceQueueMap != null) {
            mDeviceQueueMap.clear();
            mDeviceQueueMap.clear();
        }
        }
@@ -264,18 +273,6 @@ public class LeAudioServiceTest {
        LeAudioNativeInterface.setInstance(null);
        LeAudioNativeInterface.setInstance(null);
    }
    }


    private void startService() throws TimeoutException {
        TestUtils.startService(mServiceRule, LeAudioService.class);
        mService = LeAudioService.getLeAudioService();
        assertThat(mService).isNotNull();
    }

    private void stopService() throws TimeoutException {
        TestUtils.stopService(mServiceRule, LeAudioService.class);
        mService = LeAudioService.getLeAudioService();
        assertThat(mService).isNull();
    }

    private class LeAudioIntentReceiver extends BroadcastReceiver {
    private class LeAudioIntentReceiver extends BroadcastReceiver {
        @Override
        @Override
        public void onReceive(Context context, Intent intent) {
        public void onReceive(Context context, Intent intent) {
@@ -1542,10 +1539,8 @@ public class LeAudioServiceTest {
    @Test
    @Test
    public void testMediaContextUnavailableForAWhile() {
    public void testMediaContextUnavailableForAWhile() {


        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, true);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_UNICAST_INACTIVATE_DEVICE_BASED_ON_CONTEXT, true);
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, true);
        mFakeFlagsImpl.setFlag(Flags.FLAG_AUDIO_ROUTING_CENTRALIZATION, true);
        mService.setFeatureFlags(mFakeFlagsImpl);


        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mSingleDevice, testGroupId);
        connectTestDevice(mSingleDevice, testGroupId);