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

Commit c4399504 authored by Alexandr Shabalin's avatar Alexandr Shabalin Committed by Android (Google) Code Review
Browse files

Merge "Move selected items collapsing logic inside MediaSwitchingController." into main

parents 8441f07f 96dbd524
Loading
Loading
Loading
Loading
+61 −48
Original line number Original line Diff line number Diff line
@@ -141,11 +141,7 @@ class MediaOutputAdapterTest : SysuiTestCase() {


    @Test
    @Test
    fun getItemId_forDeviceGroup_returnsItemType() {
    fun getItemId_forDeviceGroup_returnsItemType() {
        mMediaSwitchingController.stub {
        initializeGroupSessionCollapsed()
            on { isGroupListCollapsed } doReturn true
            on { isVolumeControlEnabledForSession } doReturn true
        }
        initializeSession()


        assertThat(mMediaOutputAdapter.getItemId(1))
        assertThat(mMediaOutputAdapter.getItemId(1))
            .isEqualTo(MediaItemType.TYPE_DEVICE_GROUP.toLong())
            .isEqualTo(MediaItemType.TYPE_DEVICE_GROUP.toLong())
@@ -342,8 +338,8 @@ class MediaOutputAdapterTest : SysuiTestCase() {
        }
        }
        updateAdapterWithDevices(listOf(mMediaDevice1, mMediaDevice2))
        updateAdapterWithDevices(listOf(mMediaDevice1, mMediaDevice2))


        // positions: 0 - collapsible drop down, 1 - device1, 2 - device2.
        // positions: 0 - device1, 1 - device2.
        createAndBindDeviceViewHolder(position = 2).apply {
        createAndBindDeviceViewHolder(position = 1).apply {
            assertThat(mGroupButton.visibility).isEqualTo(VISIBLE)
            assertThat(mGroupButton.visibility).isEqualTo(VISIBLE)
            assertThat(mGroupButton.contentDescription)
            assertThat(mGroupButton.contentDescription)
                .isEqualTo(
                .isEqualTo(
@@ -662,40 +658,25 @@ class MediaOutputAdapterTest : SysuiTestCase() {
            on { isGroupListCollapsed } doReturn true
            on { isGroupListCollapsed } doReturn true
            on { isVolumeControlEnabledForSession } doReturn true
            on { isVolumeControlEnabledForSession } doReturn true
        }
        }
        initializeSession()


        with(mMediaOutputAdapter) {
        mMediaItems.add(MediaItem.createGroupDividerMediaItem("Connected Speakers"))
            assertThat(itemCount).isEqualTo(2)
        mMediaItems.add(MediaItem.createDeviceGroupMediaItem())
            assertThat(getItemViewType(0)).isEqualTo(MediaItemType.TYPE_GROUP_DIVIDER)
            assertThat(getItemViewType(1)).isEqualTo(MediaItemType.TYPE_DEVICE_GROUP)
        }
    }


    @Test
        mMediaOutputAdapter = MediaOutputAdapter(mMediaSwitchingController)
    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING)
        mMediaOutputAdapter.updateItems()
    fun multipleSelectedDevices_volumeControlDisabled_notCollapseList() {
        mMediaSwitchingController.stub {
            on { isGroupListCollapsed } doReturn true
            on { isVolumeControlEnabledForSession } doReturn false
        }
        initializeSession()


        with(mMediaOutputAdapter) {
        with(mMediaOutputAdapter) {
            assertThat(itemCount).isEqualTo(2)
            assertThat(itemCount).isEqualTo(2)
            assertThat(getItemViewType(0)).isEqualTo(MediaItemType.TYPE_DEVICE)
            assertThat(getItemViewType(0)).isEqualTo(MediaItemType.TYPE_GROUP_DIVIDER)
            assertThat(getItemViewType(1)).isEqualTo(MediaItemType.TYPE_DEVICE)
            assertThat(getItemViewType(1)).isEqualTo(MediaItemType.TYPE_DEVICE_GROUP)
        }
        }
    }
    }


    @Test
    @Test
    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING)
    @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING)
    fun multipleSelectedDevices_listCollapsed_verifySessionControl() {
    fun multipleSelectedDevices_listCollapsed_verifySessionControl() {
        mMediaSwitchingController.stub {
        mMediaSwitchingController.stub { on { isVolumeControlEnabledForSession } doReturn true }
            on { isGroupListCollapsed } doReturn true
        initializeGroupSessionCollapsed()
            // TODO: remove once FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING is cleaned up.
            on { isVolumeControlEnabledForSession } doReturn true
        }
        initializeSession()


        createAndBindDeviceViewHolder(position = 1).apply {
        createAndBindDeviceViewHolder(position = 1).apply {
            assertThat(mTitleText.text.toString()).isEqualTo(TEST_SESSION_NAME)
            assertThat(mTitleText.text.toString()).isEqualTo(TEST_SESSION_NAME)
@@ -725,22 +706,9 @@ class MediaOutputAdapterTest : SysuiTestCase() {
    }
    }


    @Test
    @Test
    fun multipleSelectedDevices_expandIconClicked_verifyIndividualDevices() {
    fun multipleSelectedDevices_listExpanded_verifyIndividualDevices() {
        mMediaSwitchingController.stub {
        mMediaSwitchingController.stub { on { isVolumeControlEnabledForSession } doReturn true }
            on { isGroupListCollapsed } doReturn true
        initializeGroupSessionExpanded()
            on { isVolumeControlEnabledForSession } doReturn true
        }
        initializeSession()

        val groupDividerViewHolder =
            mMediaOutputAdapter.onCreateViewHolder(
                LinearLayout(mContext),
                MediaItemType.TYPE_GROUP_DIVIDER,
            ) as MediaGroupDividerViewHolder
        mMediaOutputAdapter.onBindViewHolder(groupDividerViewHolder, 0)

        mMediaSwitchingController.stub { on { isGroupListCollapsed } doReturn false }
        groupDividerViewHolder.mExpandButton.performClick()


        createAndBindDeviceViewHolder(position = 1).apply {
        createAndBindDeviceViewHolder(position = 1).apply {
            assertThat(mTitleText.text.toString()).isEqualTo(TEST_DEVICE_NAME_1)
            assertThat(mTitleText.text.toString()).isEqualTo(TEST_DEVICE_NAME_1)
@@ -757,6 +725,26 @@ class MediaOutputAdapterTest : SysuiTestCase() {
        }
        }
    }
    }


    @Test
    fun multipleSelectedDevices_expandIconClicked_setGroupListCollapsed() {
        mMediaSwitchingController.stub {
            on { isGroupListCollapsed } doReturn true
            on { isVolumeControlEnabledForSession } doReturn true
        }
        initializeGroupSessionCollapsed()

        val groupDividerViewHolder =
            mMediaOutputAdapter.onCreateViewHolder(
                LinearLayout(mContext),
                MediaItemType.TYPE_GROUP_DIVIDER,
            ) as MediaGroupDividerViewHolder
        mMediaOutputAdapter.onBindViewHolder(groupDividerViewHolder, 0)

        groupDividerViewHolder.mExpandButton.performClick()

        verify(mMediaSwitchingController).setGroupListCollapsed(false)
    }

    private fun contextWithTheme(context: Context) =
    private fun contextWithTheme(context: Context) =
        ContextThemeWrapper(
        ContextThemeWrapper(
            context,
            context,
@@ -784,9 +772,34 @@ class MediaOutputAdapterTest : SysuiTestCase() {
        }
        }
    }
    }


    private fun initializeSession() {
    private fun initializeGroupSessionCollapsed() {
        mMediaSwitchingController.stub { on { hasGroupPlayback() } doReturn true }
        mMediaSwitchingController.stub {
            on { isGroupListCollapsed } doReturn true
            on { hasGroupPlayback() } doReturn true
        }

        mMediaItems.add(
            MediaItem.createExpandableGroupDividerMediaItem(
                mContext.getString(R.string.media_output_group_title_connected_speakers)
            )
        )
        mMediaItems.add(MediaItem.createDeviceGroupMediaItem())

        mMediaOutputAdapter = MediaOutputAdapter(mMediaSwitchingController)
        mMediaOutputAdapter.updateItems()
    }

    private fun initializeGroupSessionExpanded() {
        mMediaSwitchingController.stub {
            on { isGroupListCollapsed } doReturn false
            on { hasGroupPlayback() } doReturn true
        }


        mMediaItems.add(
            MediaItem.createExpandableGroupDividerMediaItem(
                mContext.getString(R.string.media_output_group_title_connected_speakers)
            )
        )
        mMediaDevice1.stub {
        mMediaDevice1.stub {
            on { isSelected() } doReturn true
            on { isSelected() } doReturn true
            on { isSelectable() } doReturn true
            on { isSelectable() } doReturn true
+0 −45
Original line number Original line Diff line number Diff line
@@ -53,63 +53,18 @@ import com.google.android.material.slider.Slider
/** A RecyclerView adapter for the legacy UI media output dialog device list. */
/** A RecyclerView adapter for the legacy UI media output dialog device list. */
class MediaOutputAdapter(controller: MediaSwitchingController) :
class MediaOutputAdapter(controller: MediaSwitchingController) :
    MediaOutputAdapterBase(controller) {
    MediaOutputAdapterBase(controller) {
    private var mGroupSelectedItems: Boolean? = null // Unset until the first render.


    /** Refreshes the RecyclerView dataset and forces re-render. */
    /** Refreshes the RecyclerView dataset and forces re-render. */
    override fun updateItems() {
    override fun updateItems() {
        if (mGroupSelectedItems == null) {
            // Decide whether to group devices only during the initial render.
            // Avoid grouping broadcast devices because grouped volume control is not available for
            // broadcast session.
            mGroupSelectedItems =
                mController.hasGroupPlayback() &&
                    (!Flags.enableOutputSwitcherPersonalAudioSharing() ||
                        mController.isVolumeControlEnabledForSession)
        }

        val newList =
        val newList =
            mController.getMediaItemList(false /* addConnectNewDeviceButton */).toMutableList()
            mController.getMediaItemList(false /* addConnectNewDeviceButton */).toMutableList()


        addSeparatorForTheFirstGroupDivider(newList)
        coalesceSelectedDevices(newList)

        mMediaItemList.clear()
        mMediaItemList.clear()
        mMediaItemList.addAll(newList)
        mMediaItemList.addAll(newList)


        notifyDataSetChanged()
        notifyDataSetChanged()
    }
    }


    private fun addSeparatorForTheFirstGroupDivider(newList: MutableList<MediaItem>) {
        for ((i, item) in newList.withIndex()) {
            if (item.mediaItemType == TYPE_GROUP_DIVIDER) {
                newList[i] = MediaItem.createGroupDividerWithSeparatorMediaItem(item.title)
                break
            }
        }
    }

    /**
     * If there are 2+ selected devices, adds an "Connected speakers" expandable group divider and
     * displays a single session control instead of individual device controls.
     */
    private fun coalesceSelectedDevices(newList: MutableList<MediaItem>) {
        val selectedDevices = newList.filter { this.isSelectedDevice(it) }

        if (mGroupSelectedItems == true && selectedDevices.size > 1) {
            newList.removeAll(selectedDevices.toSet())
            if (mController.isGroupListCollapsed) {
                newList.add(0, MediaItem.createDeviceGroupMediaItem())
            } else {
                newList.addAll(0, selectedDevices)
            }
            newList.add(0, mController.connectedSpeakersExpandableGroupDivider)
        }
    }

    private fun isSelectedDevice(mediaItem: MediaItem): Boolean {
        return mediaItem.mediaDevice.getOrNull()?.isSelected ?: false
    }

    override fun getItemId(position: Int): Long {
    override fun getItemId(position: Int): Long {
        if (position >= mMediaItemList.size) {
        if (position >= mMediaItemList.size) {
            Log.e(TAG, "Item position exceeds list size: $position")
            Log.e(TAG, "Item position exceeds list size: $position")
+51 −3
Original line number Original line Diff line number Diff line
@@ -22,6 +22,8 @@ import static android.media.RoutingChangeInfo.ENTRY_POINT_SYSTEM_OUTPUT_SWITCHER
import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;


import static com.android.media.flags.Flags.allowOutputSwitcherListRearrangementWithinTimeout;
import static com.android.media.flags.Flags.allowOutputSwitcherListRearrangementWithinTimeout;
import static com.android.media.flags.Flags.enableOutputSwitcherRedesign;
import static com.android.systemui.media.dialog.MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;


import android.app.KeyguardManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.Notification;
@@ -158,6 +160,7 @@ public class MediaSwitchingController
    private boolean mIsGroupListCollapsed = true;
    private boolean mIsGroupListCollapsed = true;
    private boolean mHasAdjustVolumeUserRestriction = false;
    private boolean mHasAdjustVolumeUserRestriction = false;
    private long mStartTime;
    private long mStartTime;
    @Nullable private Boolean mGroupSelectedItems = null; // Unset until the first render.


    @VisibleForTesting
    @VisibleForTesting
    final InputRouteManager.InputDeviceCallback mInputDeviceCallback =
    final InputRouteManager.InputDeviceCallback mInputDeviceCallback =
@@ -321,6 +324,14 @@ public class MediaSwitchingController
        boolean isListEmpty = mOutputMediaItemListProxy.isEmpty();
        boolean isListEmpty = mOutputMediaItemListProxy.isEmpty();
        if (isListEmpty || !mIsRefreshing) {
        if (isListEmpty || !mIsRefreshing) {
            buildMediaItems(devices);
            buildMediaItems(devices);
            if (mGroupSelectedItems == null) {
                // Decide whether to group devices only during the initial render.
                // Avoid grouping broadcast devices because grouped volume control is not
                // available for broadcast session.
                mGroupSelectedItems =
                        hasGroupPlayback() && (!Flags.enableOutputSwitcherPersonalAudioSharing()
                                || isVolumeControlEnabledForSession());
            }
            mCallback.onDeviceListChanged();
            mCallback.onDeviceListChanged();
        } else {
        } else {
            synchronized (mMediaDevicesLock) {
            synchronized (mMediaDevicesLock) {
@@ -638,10 +649,13 @@ public class MediaSwitchingController
    }
    }


    boolean hasGroupPlayback() {
    boolean hasGroupPlayback() {
        long selectedCount = mOutputMediaItemListProxy.getOutputMediaItemList().stream()
        return getSelectedDeviceItems().size() > 1;
    }

    List<MediaItem> getSelectedDeviceItems() {
        return mOutputMediaItemListProxy.getOutputMediaItemList().stream()
                .filter(item -> item.getMediaDevice().map(MediaDevice::isSelected).orElse(
                .filter(item -> item.getMediaDevice().map(MediaDevice::isSelected).orElse(
                        false)).count();
                        false)).toList();
        return selectedCount > 1;
    }
    }


    @Nullable
    @Nullable
@@ -701,12 +715,46 @@ public class MediaSwitchingController
    private List<MediaItem> getOutputDeviceList(boolean addConnectDeviceButton) {
    private List<MediaItem> getOutputDeviceList(boolean addConnectDeviceButton) {
        List<MediaItem> mediaItems = new ArrayList<>(
        List<MediaItem> mediaItems = new ArrayList<>(
                mOutputMediaItemListProxy.getOutputMediaItemList());
                mOutputMediaItemListProxy.getOutputMediaItemList());
        if (enableOutputSwitcherRedesign()) {
            addSeparatorForTheFirstGroupDivider(mediaItems);
            coalesceSelectedDevices(mediaItems);
        }
        if (addConnectDeviceButton) {
        if (addConnectDeviceButton) {
            attachConnectNewDeviceItemIfNeeded(mediaItems);
            attachConnectNewDeviceItemIfNeeded(mediaItems);
        }
        }
        return mediaItems;
        return mediaItems;
    }
    }



    private void addSeparatorForTheFirstGroupDivider(List<MediaItem> outputList) {
        for (int i = 0; i < outputList.size(); i++) {
            MediaItem item = outputList.get(i);
            if (item.getMediaItemType() == TYPE_GROUP_DIVIDER) {
                outputList.set(i,
                        MediaItem.createGroupDividerWithSeparatorMediaItem(item.getTitle()));
                break;
            }
        }
    }

    /**
     * If there are 2+ selected devices, adds an "Connected speakers" expandable group divider and
     * displays a single session control instead of individual device controls.
     */
    private void coalesceSelectedDevices(List<MediaItem> outputList) {
        List<MediaItem> selectedDevices = getSelectedDeviceItems();

        if (Boolean.TRUE.equals(mGroupSelectedItems) && hasGroupPlayback()) {
            outputList.removeAll(selectedDevices);
            if (isGroupListCollapsed()) {
                outputList.addFirst(MediaItem.createDeviceGroupMediaItem());
            } else {
                outputList.addAll(0, selectedDevices);
            }
            outputList.addFirst(getConnectedSpeakersExpandableGroupDivider());
        }
    }

    private void addInputDevices(List<MediaItem> mediaItems) {
    private void addInputDevices(List<MediaItem> mediaItems) {
        mediaItems.add(
        mediaItems.add(
                MediaItem.createGroupDividerMediaItem(
                MediaItem.createGroupDividerMediaItem(
+147 −1
Original line number Original line Diff line number Diff line
@@ -18,6 +18,10 @@ package com.android.systemui.media.dialog;


import static android.media.RoutingChangeInfo.ENTRY_POINT_SYSTEM_OUTPUT_SWITCHER;
import static android.media.RoutingChangeInfo.ENTRY_POINT_SYSTEM_OUTPUT_SWITCHER;


import static com.android.systemui.media.dialog.MediaItem.MediaItemType.TYPE_DEVICE;
import static com.android.systemui.media.dialog.MediaItem.MediaItemType.TYPE_DEVICE_GROUP;
import static com.android.systemui.media.dialog.MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertThat;


import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +32,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
@@ -700,7 +705,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
            if (item.getMediaDevice().isPresent()) {
            if (item.getMediaDevice().isPresent()) {
                devices.add(item.getMediaDevice().get());
                devices.add(item.getMediaDevice().get());
            }
            }
            if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
            if (item.getMediaItemType() == TYPE_GROUP_DIVIDER) {
                dividerSize++;
                dividerSize++;
            }
            }
        }
        }
@@ -743,6 +748,147 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
        assertThat(mMediaSwitchingController.hasMutingExpectedDevice()).isFalse();
        assertThat(mMediaSwitchingController.hasMutingExpectedDevice()).isFalse();
    }
    }


    @Test
    @EnableFlags({
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_REDESIGN,
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING
    })
    public void onDeviceListUpdate_groupPlaybackAndExpanded_allSelectedDevicesOnTop() {
        when(mMediaDevice1.isSelected()).thenReturn(true);
        when(mMediaDevice2.isSelected()).thenReturn(true);
        mMediaSwitchingController.setGroupListCollapsed(false);

        doAnswer(invocation -> {
            LocalMediaManager.DeviceCallback callback = invocation.getArgument(0);
            callback.onDeviceListUpdate(mMediaDevices);
            return null;
        }).when(mLocalMediaManager).registerCallback(any());
        doReturn(true).when(mLocalMediaManager).isMediaSessionAvailableForVolumeControl();

        mMediaSwitchingController.start(mCb);

        List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();

        assertThat(resultList.get(0).getMediaItemType()).isEqualTo(TYPE_GROUP_DIVIDER);
        assertThat(resultList.get(0).getTitle()).isEqualTo(
                mContext.getString(R.string.media_output_group_title_connected_speakers));
        assertThat(resultList.get(0).isExpandableDivider()).isTrue();

        assertThat(resultList.get(1).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice1);

        assertThat(resultList.get(2).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(2).getMediaDevice().get()).isEqualTo(mMediaDevice2);

        assertThat(resultList.size()).isEqualTo(3);
    }

    @Test
    @EnableFlags({
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_REDESIGN,
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING
    })
    public void onDeviceListUpdate_groupPlaybackAndCollapsed_groupControlAtTheTop() {
        when(mMediaDevice1.isSelected()).thenReturn(true);
        when(mMediaDevice2.isSelected()).thenReturn(true);
        mMediaSwitchingController.setGroupListCollapsed(true);

        doAnswer(invocation -> {
            LocalMediaManager.DeviceCallback callback = invocation.getArgument(0);
            callback.onDeviceListUpdate(mMediaDevices);
            return null;
        }).when(mLocalMediaManager).registerCallback(any());
        doReturn(true).when(mLocalMediaManager).isMediaSessionAvailableForVolumeControl();

        mMediaSwitchingController.start(mCb);
        List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();

        assertThat(resultList.get(0).getMediaItemType()).isEqualTo(TYPE_GROUP_DIVIDER);
        assertThat(resultList.get(0).getTitle()).isEqualTo(
                mContext.getString(R.string.media_output_group_title_connected_speakers));
        assertThat(resultList.get(0).isExpandableDivider()).isTrue();

        assertThat(resultList.get(1).getMediaItemType()).isEqualTo(TYPE_DEVICE_GROUP);

        assertThat(resultList.size()).isEqualTo(2);
    }

    @Test
    @EnableFlags({
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_REDESIGN,
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING
    })
    public void onDeviceListUpdate_sessionVolumeUnavailable_noGroupControl() {
        when(mMediaDevice1.isSelected()).thenReturn(true);
        when(mMediaDevice2.isSelected()).thenReturn(true);
        mMediaSwitchingController.setGroupListCollapsed(true);

        doAnswer(invocation -> {
            LocalMediaManager.DeviceCallback callback = invocation.getArgument(0);
            callback.onDeviceListUpdate(mMediaDevices);
            return null;
        }).when(mLocalMediaManager).registerCallback(any());
        doReturn(false).when(mLocalMediaManager).isMediaSessionAvailableForVolumeControl();

        mMediaSwitchingController.start(mCb);

        mMediaSwitchingController.setGroupListCollapsed(true);
        mMediaSwitchingController.clearMediaItemList();
        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);

        List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();

        assertThat(resultList.get(0).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);

        assertThat(resultList.get(1).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(1).getMediaDevice().get()).isEqualTo(mMediaDevice2);

        assertThat(resultList.size()).isEqualTo(2);
    }

    @Test
    @EnableFlags({
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_REDESIGN,
            Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING
    })
    public void onDeviceListUpdate_groupPlaybackCreatedLater_noGroupControl() {
        when(mMediaDevice1.isSelected()).thenReturn(true);
        when(mMediaDevice2.isSelected()).thenReturn(false);

        mMediaSwitchingController.setGroupListCollapsed(true);
        doReturn(false).when(mLocalMediaManager).isMediaSessionAvailableForVolumeControl();

        doAnswer(invocation -> {
            LocalMediaManager.DeviceCallback callback = invocation.getArgument(0);
            callback.onDeviceListUpdate(mMediaDevices);
            return null;
        }).when(mLocalMediaManager).registerCallback(any());

        mMediaSwitchingController.start(mCb);

        // Add second selected device after the initial update.
        when(mMediaDevice2.isSelected()).thenReturn(true);
        // Skip 2+ seconds to prevent the list cleanup on refresh.
        mClock.advanceTime(2500);
        mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);

        List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();

        assertThat(resultList.get(0).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(0).getMediaDevice().get()).isEqualTo(mMediaDevice1);

        assertThat(resultList.get(1).getMediaItemType()).isEqualTo(TYPE_GROUP_DIVIDER);
        assertThat(resultList.get(1).hasTopSeparator()).isTrue();
        assertThat(resultList.get(1).getTitle()).isEqualTo(
                mContext.getString(R.string.media_output_group_title_speakers_and_displays));

        assertThat(resultList.get(2).getMediaItemType()).isEqualTo(TYPE_DEVICE);
        assertThat(resultList.get(2).getMediaDevice().get()).isEqualTo(mMediaDevice2);

        assertThat(resultList.size()).isEqualTo(3);
    }

    @Test
    @Test
    public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
    public void onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
        mMediaSwitchingController.start(mCb);
        mMediaSwitchingController.start(mCb);