content: /*
   * Copyright (C) 2019 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.google.android.exoplayer2;
  
  import static com.google.common.truth.Truth.assertThat;
  import static org.junit.Assert.assertNotSame;
  import static org.junit.Assert.assertSame;
  import static org.mockito.ArgumentMatchers.any;
  import static org.mockito.ArgumentMatchers.isNull;
  import static org.mockito.Mockito.mock;
  import static org.mockito.Mockito.times;
  import static org.mockito.Mockito.verify;
  import static org.mockito.Mockito.when;
  
  import android.os.Looper;
  import androidx.test.core.app.ApplicationProvider;
  import androidx.test.ext.junit.runners.AndroidJUnit4;
  import com.google.android.exoplayer2.analytics.AnalyticsCollector;
  import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector;
  import com.google.android.exoplayer2.analytics.PlayerId;
  import com.google.android.exoplayer2.source.MediaSource;
  import com.google.android.exoplayer2.source.ShuffleOrder;
  import com.google.android.exoplayer2.testutil.FakeMediaSource;
  import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
  import com.google.android.exoplayer2.util.Clock;
  import com.google.android.exoplayer2.util.Util;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  import org.junit.Before;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  
  /** Unit test for {@link MediaSourceList}. */
  @RunWith(AndroidJUnit4.class)
  public class MediaSourceListTest {
  
    private static final int MEDIA_SOURCE_LIST_SIZE = 4;
    private static final MediaItem MINIMAL_MEDIA_ITEM =
        new MediaItem.Builder().setMediaId("").build();
  
    private MediaSourceList mediaSourceList;
  
    @Before
    public void setUp() {
      AnalyticsCollector analyticsCollector = new DefaultAnalyticsCollector(Clock.DEFAULT);
      analyticsCollector.setPlayer(
          new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build(),
          Looper.getMainLooper());
      mediaSourceList =
          new MediaSourceList(
              mock(MediaSourceList.MediaSourceListInfoRefreshListener.class),
              analyticsCollector,
              Clock.DEFAULT.createHandler(Util.getCurrentOrMainLooper(), /* callback= */ null),
              PlayerId.UNSET);
    }
  
    @Test
    public void emptyMediaSourceList_expectConstantTimelineInstanceEMPTY() {
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 0);
      List<MediaSourceList.MediaSourceHolder> fakeHolders = createFakeHolders();
  
      Timeline timeline = mediaSourceList.setMediaSources(fakeHolders, shuffleOrder);
      assertNotSame(timeline, Timeline.EMPTY);
  
      // Remove all media sources.
      timeline =
          mediaSourceList.removeMediaSourceRange(
              /* fromIndex= */ 0, /* toIndex= */ timeline.getWindowCount(), shuffleOrder);
      assertSame(timeline, Timeline.EMPTY);
  
      timeline = mediaSourceList.setMediaSources(fakeHolders, shuffleOrder);
      assertNotSame(timeline, Timeline.EMPTY);
      // Clear.
      timeline = mediaSourceList.clear(shuffleOrder);
      assertSame(timeline, Timeline.EMPTY);
    }
  
    @Test
    public void prepareAndReprepareAfterRelease_expectSourcePreparationAfterMediaSourceListPrepare() {
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      mediaSourceList.setMediaSources(
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
      // Verify prepare is called once on prepare.
      verify(mockMediaSource1, times(0))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(0))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
  
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      assertThat(mediaSourceList.isPrepared()).isTrue();
      // Verify prepare is called once on prepare.
      verify(mockMediaSource1, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
  
      mediaSourceList.release();
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      // Verify prepare is called a second time on re-prepare.
      verify(mockMediaSource1, times(2))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(2))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
    }
  
    @Test
    public void setMediaSources_mediaSourceListUnprepared_notUsingLazyPreparation() {
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> mediaSources =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
      Timeline timeline = mediaSourceList.setMediaSources(mediaSources, shuffleOrder);
  
      assertThat(timeline.getWindowCount()).isEqualTo(2);
      assertThat(mediaSourceList.getSize()).isEqualTo(2);
  
      // Assert holder offsets have been set properly
      for (int i = 0; i < mediaSources.size(); i++) {
        MediaSourceList.MediaSourceHolder mediaSourceHolder = mediaSources.get(i);
        assertThat(mediaSourceHolder.isRemoved).isFalse();
        assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
      }
  
      // Set media items again. The second holder is re-used.
      MediaSource mockMediaSource3 = mock(MediaSource.class);
      when(mockMediaSource3.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> moreMediaSources =
          createFakeHoldersWithSources(/* useLazyPreparation= */ false, mockMediaSource3);
      moreMediaSources.add(mediaSources.get(1));
      timeline = mediaSourceList.setMediaSources(moreMediaSources, shuffleOrder);
  
      assertThat(mediaSourceList.getSize()).isEqualTo(2);
      assertThat(timeline.getWindowCount()).isEqualTo(2);
      for (int i = 0; i < moreMediaSources.size(); i++) {
        MediaSourceList.MediaSourceHolder mediaSourceHolder = moreMediaSources.get(i);
        assertThat(mediaSourceHolder.isRemoved).isFalse();
        assertThat(mediaSourceHolder.firstWindowIndexInChild).isEqualTo(i);
      }
      // Expect removed holders and sources to be removed without releasing.
      verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSources.get(0).isRemoved).isTrue();
      // Expect re-used holder and source not to be removed.
      verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSources.get(1).isRemoved).isFalse();
    }
  
    @Test
    public void setMediaSources_mediaSourceListPrepared_notUsingLazyPreparation() {
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2);
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> mediaSources =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
  
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      mediaSourceList.setMediaSources(mediaSources, shuffleOrder);
  
      // Verify sources are prepared.
      verify(mockMediaSource1, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
  
      // Set media items again. The second holder is re-used.
      MediaSource mockMediaSource3 = mock(MediaSource.class);
      when(mockMediaSource3.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> moreMediaSources =
          createFakeHoldersWithSources(/* useLazyPreparation= */ false, mockMediaSource3);
      moreMediaSources.add(mediaSources.get(1));
      mediaSourceList.setMediaSources(moreMediaSources, shuffleOrder);
  
      // Expect removed holders and sources to be removed and released.
      verify(mockMediaSource1, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSources.get(0).isRemoved).isTrue();
      // Expect re-used holder and source not to be removed but released.
      verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSources.get(1).isRemoved).isFalse();
      verify(mockMediaSource2, times(2))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
    }
  
    @Test
    public void addMediaSources_mediaSourceListUnprepared_notUsingLazyPreparation_expectUnprepared() {
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> mediaSources =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
      mediaSourceList.addMediaSources(
          /* index= */ 0, mediaSources, new ShuffleOrder.DefaultShuffleOrder(2));
  
      assertThat(mediaSourceList.getSize()).isEqualTo(2);
      // Verify lazy initialization does not call prepare on sources.
      verify(mockMediaSource1, times(0))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(0))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
  
      for (int i = 0; i < mediaSources.size(); i++) {
        assertThat(mediaSources.get(i).firstWindowIndexInChild).isEqualTo(i);
        assertThat(mediaSources.get(i).isRemoved).isFalse();
      }
  
      // Add for more sources in between.
      List<MediaSourceList.MediaSourceHolder> moreMediaSources = createFakeHolders();
      mediaSourceList.addMediaSources(
          /* index= */ 1, moreMediaSources, new ShuffleOrder.DefaultShuffleOrder(/* length= */ 3));
  
      assertThat(mediaSources.get(0).firstWindowIndexInChild).isEqualTo(0);
      assertThat(moreMediaSources.get(0).firstWindowIndexInChild).isEqualTo(1);
      assertThat(moreMediaSources.get(3).firstWindowIndexInChild).isEqualTo(4);
      assertThat(mediaSources.get(1).firstWindowIndexInChild).isEqualTo(5);
    }
  
    @Test
    public void addMediaSources_mediaSourceListPrepared_notUsingLazyPreparation_expectPrepared() {
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      mediaSourceList.addMediaSources(
          /* index= */ 0,
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2),
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 2));
  
      // Verify prepare is called on sources when added.
      verify(mockMediaSource1, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      verify(mockMediaSource2, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
    }
  
    @Test
    public void moveMediaSources() {
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
      List<MediaSourceList.MediaSourceHolder> holders = createFakeHolders();
      mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
  
      assertDefaultFirstWindowInChildIndexOrder(holders);
      mediaSourceList.moveMediaSource(/* currentIndex= */ 0, /* newIndex= */ 3, shuffleOrder);
      assertFirstWindowInChildIndices(holders, 3, 0, 1, 2);
      mediaSourceList.moveMediaSource(/* currentIndex= */ 3, /* newIndex= */ 0, shuffleOrder);
      assertDefaultFirstWindowInChildIndexOrder(holders);
  
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
      assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 2, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
      assertDefaultFirstWindowInChildIndexOrder(holders);
  
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 0, /* toIndex= */ 2, /* newFromIndex= */ 2, shuffleOrder);
      assertFirstWindowInChildIndices(holders, 2, 3, 0, 1);
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 2, /* toIndex= */ 3, /* newFromIndex= */ 0, shuffleOrder);
      assertFirstWindowInChildIndices(holders, 0, 3, 1, 2);
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 3, /* toIndex= */ 4, /* newFromIndex= */ 1, shuffleOrder);
      assertDefaultFirstWindowInChildIndexOrder(holders);
  
      // No-ops.
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 0, /* toIndex= */ 4, /* newFromIndex= */ 0, shuffleOrder);
      assertDefaultFirstWindowInChildIndexOrder(holders);
      mediaSourceList.moveMediaSourceRange(
          /* fromIndex= */ 0, /* toIndex= */ 0, /* newFromIndex= */ 3, shuffleOrder);
      assertDefaultFirstWindowInChildIndexOrder(holders);
    }
  
    @Test
    public void removeMediaSources_whenUnprepared_expectNoRelease() {
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource3 = mock(MediaSource.class);
      when(mockMediaSource3.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource4 = mock(MediaSource.class);
      when(mockMediaSource4.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
  
      List<MediaSourceList.MediaSourceHolder> holders =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false,
              mockMediaSource1,
              mockMediaSource2,
              mockMediaSource3,
              mockMediaSource4);
      mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
      mediaSourceList.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
  
      assertThat(mediaSourceList.getSize()).isEqualTo(2);
      MediaSourceList.MediaSourceHolder removedHolder1 = holders.remove(1);
      MediaSourceList.MediaSourceHolder removedHolder2 = holders.remove(1);
  
      assertDefaultFirstWindowInChildIndexOrder(holders);
      assertThat(removedHolder1.isRemoved).isTrue();
      assertThat(removedHolder2.isRemoved).isTrue();
      verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource2, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource3, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
    }
  
    @Test
    public void removeMediaSources_whenPrepared_expectRelease() {
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource3 = mock(MediaSource.class);
      when(mockMediaSource3.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource4 = mock(MediaSource.class);
      when(mockMediaSource4.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
  
      List<MediaSourceList.MediaSourceHolder> holders =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false,
              mockMediaSource1,
              mockMediaSource2,
              mockMediaSource3,
              mockMediaSource4);
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      mediaSourceList.addMediaSources(/* index= */ 0, holders, shuffleOrder);
      mediaSourceList.removeMediaSourceRange(/* fromIndex= */ 1, /* toIndex= */ 3, shuffleOrder);
  
      assertThat(mediaSourceList.getSize()).isEqualTo(2);
      holders.remove(2);
      holders.remove(1);
  
      assertDefaultFirstWindowInChildIndexOrder(holders);
      verify(mockMediaSource1, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource2, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource3, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      verify(mockMediaSource4, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
    }
  
    @Test
    public void release_mediaSourceListUnprepared_expectSourcesNotReleased() {
      MediaSource mockMediaSource = mock(MediaSource.class);
      when(mockMediaSource.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSourceList.MediaSourceHolder mediaSourceHolder =
          new MediaSourceList.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
  
      mediaSourceList.setMediaSources(
          Collections.singletonList(mediaSourceHolder),
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
      verify(mockMediaSource, times(0))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      mediaSourceList.release();
      verify(mockMediaSource, times(0)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSourceHolder.isRemoved).isFalse();
    }
  
    @Test
    public void release_mediaSourceListPrepared_expectSourcesReleasedNotRemoved() {
      MediaSource mockMediaSource = mock(MediaSource.class);
      when(mockMediaSource.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSourceList.MediaSourceHolder mediaSourceHolder =
          new MediaSourceList.MediaSourceHolder(mockMediaSource, /* useLazyPreparation= */ false);
  
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
      mediaSourceList.setMediaSources(
          Collections.singletonList(mediaSourceHolder),
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 1));
      verify(mockMediaSource, times(1))
          .prepareSource(
              any(MediaSource.MediaSourceCaller.class), /* mediaTransferListener= */ isNull(), any());
      mediaSourceList.release();
      verify(mockMediaSource, times(1)).releaseSource(any(MediaSource.MediaSourceCaller.class));
      assertThat(mediaSourceHolder.isRemoved).isFalse();
    }
  
    @Test
    public void clearMediaSourceList_expectSourcesReleasedAndRemoved() {
      ShuffleOrder.DefaultShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ 4);
      MediaSource mockMediaSource1 = mock(MediaSource.class);
      when(mockMediaSource1.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      MediaSource mockMediaSource2 = mock(MediaSource.class);
      when(mockMediaSource2.getMediaItem()).thenReturn(MINIMAL_MEDIA_ITEM);
      List<MediaSourceList.MediaSourceHolder> holders =
          createFakeHoldersWithSources(
              /* useLazyPreparation= */ false, mockMediaSource1, mockMediaSource2);
      mediaSourceList.setMediaSources(holders, shuffleOrder);
      mediaSourceList.prepare(/* mediaTransferListener= */ null);
  
      Timeline timeline = mediaSourceList.clear(shuffleOrder);
      assertThat(timeline.isEmpty()).isTrue();
      assertThat(holders.get(0).isRemoved).isTrue();
      assertThat(holders.get(1).isRemoved).isTrue();
      verify(mockMediaSource1, times(1)).releaseSource(any());
      verify(mockMediaSource2, times(1)).releaseSource(any());
    }
  
    @Test
    public void setMediaSources_expectTimelineUsesCustomShuffleOrder() {
      Timeline timeline =
          mediaSourceList.setMediaSources(createFakeHolders(), new FakeShuffleOrder(/* length=*/ 4));
      assertTimelineUsesFakeShuffleOrder(timeline);
    }
  
    @Test
    public void addMediaSources_expectTimelineUsesCustomShuffleOrder() {
      Timeline timeline =
          mediaSourceList.addMediaSources(
              /* index= */ 0, createFakeHolders(), new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
      assertTimelineUsesFakeShuffleOrder(timeline);
    }
  
    @Test
    public void moveMediaSources_expectTimelineUsesCustomShuffleOrder() {
      ShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
      mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
      Timeline timeline =
          mediaSourceList.moveMediaSource(
              /* currentIndex= */ 0, /* newIndex= */ 1, new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
      assertTimelineUsesFakeShuffleOrder(timeline);
    }
  
    @Test
    public void moveMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
      ShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
      mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
      Timeline timeline =
          mediaSourceList.moveMediaSourceRange(
              /* fromIndex= */ 0,
              /* toIndex= */ 2,
              /* newFromIndex= */ 2,
              new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE));
      assertTimelineUsesFakeShuffleOrder(timeline);
    }
  
    @Test
    public void removeMediaSourceRange_expectTimelineUsesCustomShuffleOrder() {
      ShuffleOrder shuffleOrder =
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE);
      mediaSourceList.addMediaSources(/* index= */ 0, createFakeHolders(), shuffleOrder);
      Timeline timeline =
          mediaSourceList.removeMediaSourceRange(
              /* fromIndex= */ 0, /* toIndex= */ 2, new FakeShuffleOrder(/* length= */ 2));
      assertTimelineUsesFakeShuffleOrder(timeline);
    }
  
    @Test
    public void setShuffleOrder_expectTimelineUsesCustomShuffleOrder() {
      mediaSourceList.setMediaSources(
          createFakeHolders(),
          new ShuffleOrder.DefaultShuffleOrder(/* length= */ MEDIA_SOURCE_LIST_SIZE));
      assertTimelineUsesFakeShuffleOrder(
          mediaSourceList.setShuffleOrder(new FakeShuffleOrder(MEDIA_SOURCE_LIST_SIZE)));
    }
  
    // Internal methods.
  
    private static void assertTimelineUsesFakeShuffleOrder(Timeline timeline) {
      assertThat(
              timeline.getNextWindowIndex(
                  /* windowIndex= */ 0, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
          .isEqualTo(-1);
      assertThat(
              timeline.getPreviousWindowIndex(
                  /* windowIndex= */ timeline.getWindowCount() - 1,
                  Player.REPEAT_MODE_OFF,
                  /* shuffleModeEnabled= */ true))
          .isEqualTo(-1);
    }
  
    private static void assertDefaultFirstWindowInChildIndexOrder(
        List<MediaSourceList.MediaSourceHolder> holders) {
      int[] indices = new int[holders.size()];
      for (int i = 0; i < indices.length; i++) {
        indices[i] = i;
      }
      assertFirstWindowInChildIndices(holders, indices);
    }
  
    private static void assertFirstWindowInChildIndices(
        List<MediaSourceList.MediaSourceHolder> holders, int... firstWindowInChildIndices) {
      assertThat(holders).hasSize(firstWindowInChildIndices.length);
      for (int i = 0; i < holders.size(); i++) {
        assertThat(holders.get(i).firstWindowIndexInChild).isEqualTo(firstWindowInChildIndices[i]);
      }
    }
  
    private static List<MediaSourceList.MediaSourceHolder> createFakeHolders() {
      List<MediaSourceList.MediaSourceHolder> holders = new ArrayList<>();
      for (int i = 0; i < MEDIA_SOURCE_LIST_SIZE; i++) {
        holders.add(
            new MediaSourceList.MediaSourceHolder(
                new FakeMediaSource(), /* useLazyPreparation= */ true));
      }
      return holders;
    }
  
    private static List<MediaSourceList.MediaSourceHolder> createFakeHoldersWithSources(
        boolean useLazyPreparation, MediaSource... sources) {
      List<MediaSourceList.MediaSourceHolder> holders = new ArrayList<>();
      for (MediaSource mediaSource : sources) {
        holders.add(
            new MediaSourceList.MediaSourceHolder(
                mediaSource, /* useLazyPreparation= */ useLazyPreparation));
      }
      return holders;
    }
  }
  