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

Commit 5f776e72 authored by Chung Tang's avatar Chung Tang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "output-switcher-lea-sharing" into main

* changes:
  [OutputSwitcher] Support select and deselect Le Audio devices for Le Audio Sharing
  [OutputSwitcher] Add config for indicating max devices supported for le audio sharing
parents e961eb84 dde85a99
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -7564,4 +7564,7 @@

    <!-- Whether Sys UI reboot UI is prioritized for reocvery triggered UI. -->
    <bool name="config_prioritizeSysUiForRecoveryRebootUi">false</bool>

    <!-- Maximum number of devices that allows for audio sharing. -->
    <integer name="config_audio_sharing_maximum_sinks">2</integer>
</resources>
+3 −0
Original line number Diff line number Diff line
@@ -6212,4 +6212,7 @@

  <!-- Whether Sys UI reboot UI is prioritized for reocvery triggered UI. -->
  <java-symbol type="bool" name="config_prioritizeSysUiForRecoveryRebootUi" />

  <!-- Maximum number of devices that allows for audio sharing. -->
  <java-symbol type="integer" name="config_audio_sharing_maximum_sinks" />
</resources>
+98 −14
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -116,7 +117,7 @@ import java.util.concurrent.CopyOnWriteArrayList;

    @GuardedBy("this")
    @NonNull
    private MediaRoute2Info mSelectedRoute;
    private final List<MediaRoute2Info> mSelectedRoutes = new ArrayList<>();

    // A singleton AudioManagerRouteController.
    private static AudioManagerRouteController mInstance;
@@ -243,10 +244,27 @@ import java.util.concurrent.CopyOnWriteArrayList;
        // TODO(b/385672684): impl release system session
    }

    @RequiresPermission(
            anyOf = {
                Manifest.permission.MODIFY_AUDIO_ROUTING,
                Manifest.permission.QUERY_AUDIO_STATE
            })
    @Override
    public int getAudioDeviceType() {
        List<AudioDeviceAttributes> audioDevices =
                mAudioManager.getDevicesForAttributes(
                        new AudioAttributes.Builder()
                                .setUsage(AudioAttributes.USAGE_MEDIA)
                                .build());
        return audioDevices.isEmpty()
                ? AudioDeviceInfo.TYPE_UNKNOWN
                : audioDevices.getFirst().getType();
    }

    @Override
    @NonNull
    public synchronized MediaRoute2Info getSelectedRoute() {
        return mSelectedRoute;
    public synchronized List<MediaRoute2Info> getSelectedRoutes() {
        return mSelectedRoutes;
    }

    @Override
@@ -296,6 +314,39 @@ import java.util.concurrent.CopyOnWriteArrayList;
        mHandler.post(guardedTransferAction);
    }

    @Override
    public void selectRoute(String routeId) {
        if (isBroadcasting()) {
            // Currently we do not allow selecting route when already broadcasting,
            // Ui should block user from select route as well.
            Slog.e(TAG, "Unable to select route: already broadcasting");
            return;
        }

        // Construct the list of routeIds
        List<String> routeIdListForBroadcast =
                new ArrayList<>(getSelectedRoutes().stream().map(MediaRoute2Info::getId).toList());
        routeIdListForBroadcast.add(routeId);

        // Then start private broadcast
        mHandler.post(
                () -> mBluetoothRouteController.startPrivateBroadcast(routeIdListForBroadcast));
    }

    @Override
    public synchronized void deselectRoute() {
        if (!isBroadcasting()) {
            // Unexpected result.
            Slog.e(TAG, "Unable to deselect route: no broadcasting");
            return;
        }

        // TODO: b/414535608 - Handle PAS with 3+ devices
        // Currently only max 2 devices are supported for audio sharing, deselecting one means stop
        // broadcasting and transfer to audio to the only not being deselected.
        mHandler.post(mBluetoothRouteController::stopBroadcast);
    }

    @RequiresPermission(
            anyOf = {
                Manifest.permission.MODIFY_AUDIO_ROUTING,
@@ -418,8 +469,32 @@ import java.util.concurrent.CopyOnWriteArrayList;
            int musicMaxVolume,
            boolean isVolumeFixed) {
        mRouteIdToAvailableDeviceRoutes.clear();
        MediaRoute2InfoHolder newSelectedRouteHolder = null;
        mSelectedRoutes.clear();
        List<MediaRoute2InfoHolder> newSelectedRouteHolders = new ArrayList<>();

        // When do audio sharing, the audioDeviceInfos obtains from AudioManager is not reliable.
        // Special handling is needed.
        if (com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing()) {
            if (selectedDeviceAttributesType == AudioDeviceInfo.TYPE_BLE_BROADCAST) {
                for (MediaRoute2Info mediaRoute2Info :
                        mBluetoothRouteController.getBroadcastingDeviceRoutes()) {
                    // Need to reconstruct MediaRoute2Info from BluetoothDeviceRoutesController
                    MediaRoute2InfoHolder newHolder =
                            MediaRoute2InfoHolder.createForAudioManagerRoute(
                                    mediaRoute2Info, AudioDeviceInfo.TYPE_BLE_HEADSET);
                    mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder);
                    newSelectedRouteHolders.add(newHolder);
                }
            }
        }

        for (AudioDeviceInfo audioDeviceInfo : audioDeviceInfos) {
            if (com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing()) {
                if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_BLE_BROADCAST) {
                    // Handled previously
                    continue;
                }
            }
            MediaRoute2Info mediaRoute2Info =
                    createMediaRoute2InfoFromAudioDeviceInfo(audioDeviceInfo);
            // Null means audioDeviceInfo is not a supported media output, like a phone's builtin
@@ -431,7 +506,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
                                mediaRoute2Info, audioDeviceInfoType);
                mRouteIdToAvailableDeviceRoutes.put(mediaRoute2Info.getId(), newHolder);
                if (selectedDeviceAttributesType == audioDeviceInfoType) {
                    newSelectedRouteHolder = newHolder;
                    newSelectedRouteHolders.add(newHolder);
                }
            }
        }
@@ -447,7 +522,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
            mRouteIdToAvailableDeviceRoutes.put(placeholderRouteId, placeholderRouteHolder);
        }

        if (newSelectedRouteHolder == null) {
        if (newSelectedRouteHolders.isEmpty()) {
            Slog.e(
                    TAG,
                    "Could not map this selected device attribute type to an available route: "
@@ -458,15 +533,20 @@ import java.util.concurrent.CopyOnWriteArrayList;
                                            .map(AudioDeviceInfo::getType)
                                            .toArray()));
            // We know mRouteIdToAvailableDeviceRoutes is not empty.
            newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
            newSelectedRouteHolders.add(mRouteIdToAvailableDeviceRoutes.values().iterator().next());
        }

        for (MediaRoute2InfoHolder newSelectedRouteHolder : newSelectedRouteHolders) {
            MediaRoute2InfoHolder selectedRouteHolderWithUpdatedVolumeInfo =
                    newSelectedRouteHolder.copyWithVolumeInfo(
                            musicVolume, musicMaxVolume, isVolumeFixed);

            mRouteIdToAvailableDeviceRoutes.put(
                    newSelectedRouteHolder.mMediaRoute2Info.getId(),
                    selectedRouteHolderWithUpdatedVolumeInfo);
        mSelectedRoute = selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info;

            mSelectedRoutes.add(selectedRouteHolderWithUpdatedVolumeInfo.mMediaRoute2Info);
        }

        // We only add those BT routes that we have not already obtained from audio manager (which
        // are active).
@@ -557,6 +637,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
        return builder.build();
    }

    private boolean isBroadcasting() {
        return getAudioDeviceType() == AudioDeviceInfo.TYPE_BLE_BROADCAST;
    }

    /**
     * Holds a {@link MediaRoute2Info} and associated information that we don't want to put in the
     * {@link MediaRoute2Info} class because it's solely necessary for the implementation of this
+48 −0
Original line number Diff line number Diff line
@@ -215,6 +215,54 @@ import java.util.stream.Collectors;
        return routes;
    }

    /**
     * Trigger {@link BluetoothProfileMonitor} to start broadcast.
     *
     * @param targetRouteIds routes ids that broadcast targeting to
     */
    protected void startPrivateBroadcast(List<String> targetRouteIds) {
        if (targetRouteIds.size() <= 1) {
            Log.e(TAG, "Unable to start broadcast, incorrect number of routes.");
            return;
        }

        // Filter the list to only contains items with matching route ids, then
        // Map the list to BluetoothDevice list to start the broadcast.
        List<BluetoothDevice> deviceListForBroadcast = new ArrayList<>();

        // Check if routeInfo are in the target list, and
        // Prevent duplicated entries
        for (BluetoothRouteInfo routeInfo : mBluetoothRoutes.values()) {
            if (targetRouteIds.contains(routeInfo.mRoute.getId())
                    && !deviceListForBroadcast.contains(routeInfo.mBtDevice)) {
                deviceListForBroadcast.add(routeInfo.mBtDevice);
            }
        }

        mBluetoothProfileMonitor.startPrivateBroadcast(deviceListForBroadcast);
    }

    /** Trigger {@link BluetoothProfileMonitor} to stop broadcast. */
    protected void stopBroadcast() {
        mBluetoothProfileMonitor.stopPrivateBroadcast();
    }

    /**
     * Obtains a list of selected bluetooth route infos.
     *
     * @return list of selected bluetooth route infos.
     */
    public List<MediaRoute2Info> getBroadcastingDeviceRoutes() {
        // Use HashSet to check and avoid duplicates devices with same routeId
        Set<String> routeIdSet = new HashSet<>();

        // Convert List<BluetoothDevice> to List<MediaRoute2Info>
        return mBluetoothProfileMonitor.getBroadcastingDevices().stream()
                .map(device -> createBluetoothRoute(device).mRoute)
                .filter(routeInfo -> routeIdSet.add(routeInfo.getId()))
                .toList();
    }

    private void notifyBluetoothRoutesUpdated() {
        mListener.onBluetoothRoutesUpdated();
    }
+393 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading