/* * Copyright (C) 2017 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 android.support.v17.leanback.widget; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.v17.leanback.media.PlaybackTransportControlGlue; import android.support.v17.leanback.media.PlayerAdapter; import android.support.v17.leanback.widget.PlaybackSeekDataProvider.ResultCallback; import android.view.ContextThemeWrapper; import android.view.KeyEvent; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import java.util.Arrays; @SmallTest public class PlaybackTransportRowPresenterTest { Context mContext; PlaybackTransportControlGlue mGlue; PlaybackGlueHostImplWithViewHolder mHost; PlayerAdapter mImpl; PlaybackTransportRowPresenter.ViewHolder mViewHolder; AbstractDetailsDescriptionPresenter.ViewHolder mDescriptionViewHolder; int mNumbThumbs; @Before public void setUp() { mContext = new ContextThemeWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext(), android.support.v17.leanback.test.R.style.Theme_Leanback); mHost = new PlaybackGlueHostImplWithViewHolder(mContext); mImpl = Mockito.mock(PlayerAdapter.class); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mGlue = new PlaybackTransportControlGlue(mContext, mImpl); mGlue.setHost(mHost); } }); mViewHolder = (PlaybackTransportRowPresenter.ViewHolder) mHost.mViewHolder; mDescriptionViewHolder = (AbstractDetailsDescriptionPresenter.ViewHolder) mViewHolder.mDescriptionViewHolder; mNumbThumbs = mViewHolder.mThumbsBar.getChildCount(); assertTrue((mNumbThumbs & 1) != 0); } void sendKeyUIThread(int keyCode) { sendKeyUIThread(keyCode, 1); } void sendKeyUIThread(final int keyCode, final int repeat) { InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mHost.sendKeyDownUp(keyCode, repeat); } }); } void verifyGetThumbCalls(int firstHeroIndex, int lastHeroIndex, PlaybackSeekDataProvider provider, long[] positions) { int firstThumbIndex = Math.max(firstHeroIndex - (mNumbThumbs / 2), 0); int lastThumbIndex = Math.min(lastHeroIndex + (mNumbThumbs / 2), positions.length - 1); for (int i = firstThumbIndex; i <= lastThumbIndex; i++) { Mockito.verify(provider, times(1)).getThumbnail(eq(i), any(ResultCallback.class)); } Mockito.verify(provider, times(0)).getThumbnail( eq(firstThumbIndex - 1), any(ResultCallback.class)); Mockito.verify(provider, times(0)).getThumbnail( eq(firstThumbIndex - 2), any(ResultCallback.class)); Mockito.verify(provider, times(0)).getThumbnail( eq(lastThumbIndex + 1), any(ResultCallback.class)); Mockito.verify(provider, times(0)).getThumbnail( eq(lastThumbIndex + 2), any(ResultCallback.class)); } void verifyAtHeroIndexWithDifferentPosition(long position, int heroIndex) { assertEquals(position, mGlue.getControlsRow().getCurrentPosition()); assertEquals(mViewHolder.mThumbHeroIndex, heroIndex); } void verifyAtHeroIndex(long[] positions, int heroIndex) { verifyAtHeroIndex(positions, heroIndex, null); } void verifyAtHeroIndex(long[] positions, int heroIndex, Bitmap[] thumbs) { assertEquals(positions[heroIndex], mGlue.getControlsRow().getCurrentPosition()); assertEquals(mViewHolder.mThumbHeroIndex, heroIndex); if (thumbs != null) { int start = Math.max(0, mViewHolder.mThumbHeroIndex - mNumbThumbs / 2); int end = Math.min(positions.length - 1, mViewHolder.mThumbHeroIndex + mNumbThumbs / 2); verifyThumbBitmaps(thumbs, start, end, mViewHolder.mThumbsBar, start + mNumbThumbs / 2 - mViewHolder.mThumbHeroIndex, end + mNumbThumbs / 2 - mViewHolder.mThumbHeroIndex); } } void verifyThumbBitmaps(Bitmap[] thumbs, int start, int end, ThumbsBar thumbsBar, int childStart, int childEnd) { assertEquals(end - start, childEnd - childStart); for (int i = start; i <= end; i++) { assertSame(thumbs[i], thumbsBar.getThumbBitmap(childStart + (i - start))); } for (int i = 0; i < childStart; i++) { assertNull(thumbsBar.getThumbBitmap(i)); } for (int i = childEnd + 1; i < mNumbThumbs; i++) { assertNull(thumbsBar.getThumbBitmap(i)); } } @Test public void progressUpdating() { when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(123L); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mGlue.play(); Mockito.verify(mImpl, times(1)).play(); mGlue.pause(); Mockito.verify(mImpl, times(1)).pause(); mGlue.seekTo(1231); Mockito.verify(mImpl, times(1)).seekTo(1231); mImpl.getCallback().onCurrentPositionChanged(mImpl); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); assertEquals(123L, mGlue.getCurrentPosition()); assertEquals(20000L, mGlue.getDuration()); assertEquals(321L, mGlue.getBufferedPosition()); assertEquals(123L, mViewHolder.mCurrentTimeInMs); assertEquals(20000L, mViewHolder.mTotalTimeInMs); assertEquals(321L, mViewHolder.mSecondaryProgressInMs); when(mImpl.getCurrentPosition()).thenReturn(124L); mImpl.getCallback().onCurrentPositionChanged(mImpl); assertEquals(124L, mGlue.getControlsRow().getCurrentPosition()); assertEquals(124L, mViewHolder.mCurrentTimeInMs); when(mImpl.getBufferedPosition()).thenReturn(333L); mImpl.getCallback().onBufferedPositionChanged(mImpl); assertEquals(333L, mGlue.getControlsRow().getBufferedPosition()); assertEquals(333L, mViewHolder.mSecondaryProgressInMs); when(mImpl.getDuration()).thenReturn((long) (Integer.MAX_VALUE) * 2); mImpl.getCallback().onDurationChanged(mImpl); assertEquals((long) (Integer.MAX_VALUE) * 2, mGlue.getControlsRow().getDuration()); assertEquals((long) (Integer.MAX_VALUE) * 2, mViewHolder.mTotalTimeInMs); } @Test public void mediaInfo() { final ColorDrawable art = new ColorDrawable(); InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mGlue.setTitle("xyz"); mGlue.setSubtitle("zyx"); mGlue.setArt(art); } }); assertEquals("xyz", mDescriptionViewHolder.mTitle.getText()); assertEquals("zyx", mDescriptionViewHolder.mSubtitle.getText()); assertSame(art, mViewHolder.mImageView.getDrawable()); } @Test public void seekAndConfirm() { when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(0L); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onCurrentPositionChanged(mImpl); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 2); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_CENTER); Mockito.verify(mImpl).seekTo(positions[2]); verifyGetThumbCalls(1, 2, provider, positions); } @Test public void seekHoldKeyDown() { when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(4489L); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(4489L); mImpl.getCallback().onCurrentPositionChanged(mImpl); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); int insertPosition = -1 - Arrays.binarySearch(positions, 4489L); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT, 5); verifyAtHeroIndex(positions, insertPosition + 4); verifyGetThumbCalls(insertPosition, insertPosition + 4, provider, positions); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT, 5); verifyAtHeroIndex(positions, insertPosition - 1); } @Test public void seekAndCancel() { when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(0L); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onCurrentPositionChanged(mImpl); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 2); sendKeyUIThread(KeyEvent.KEYCODE_BACK); Mockito.verify(mImpl, times(0)).seekTo(anyInt()); verifyGetThumbCalls(1, 2, provider, positions); } @Test public void seekUpBetweenTwoKeyPosition() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); // initially select between 0 and 1 when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 1); verifyGetThumbCalls(1, 1, provider, positions); } @Test public void seekDownBetweenTwoKeyPosition() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); assertTrue(positions[0] == 0); // initially select between 0 and 1 when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndex(positions, 0); verifyGetThumbCalls(0, 0, provider, positions); } @Test public void seekDownOutOfKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(1000L, 10000L, 101)); final long[] positions = provider.getSeekPositions(); assertTrue(positions[0] > 0); // initially select between 0 and 1 when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndex(positions, 0); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndexWithDifferentPosition(0, 0); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndexWithDifferentPosition(0, 0); verifyGetThumbCalls(0, 0, provider, positions); } @Test public void seekDownAheadOfKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(1000L, 10000L, 101)); final long[] positions = provider.getSeekPositions(); assertTrue(positions[0] > 0); // initially select between 0 and 1 when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(positions[0] / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndexWithDifferentPosition(0, 0); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 0); verifyGetThumbCalls(0, 0, provider, positions); } @Test public void seekUpAheadOfKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(1000L, 10000L, 101)); final long[] positions = provider.getSeekPositions(); assertTrue(positions[0] > 0); // initially select between 0 and 1 when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(positions[0] / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 0); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndexWithDifferentPosition(0, 0); verifyGetThumbCalls(0, 0, provider, positions); } @Test public void seekUpOutOfKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); // initially select between nth-1 and nth when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn((positions[positions.length - 2] + positions[positions.length - 1]) / 2); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, positions.length - 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndex(positions, positions.length - 2); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, positions.length - 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1); verifyGetThumbCalls(positions.length - 2, positions.length - 1, provider, positions); } @Test public void seekUpAfterKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); // initially select after last item when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(positions[positions.length - 1] + 100); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndex(positions, positions.length - 1); verifyGetThumbCalls(positions.length - 1, positions.length - 1, provider, positions); } @Test public void seekDownAfterKeyPositions() { PlaybackSeekProviderSample provider = Mockito.spy( new PlaybackSeekProviderSample(10000L, 101)); final long[] positions = provider.getSeekPositions(); // initially select after last item when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(positions[positions.length - 1] + 100); mImpl.getCallback().onCurrentPositionChanged(mImpl); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT); verifyAtHeroIndex(positions, positions.length - 1); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1); verifyGetThumbCalls(positions.length - 1, positions.length - 1, provider, positions); } @Test public void thumbLoadedInCallback() { when(mImpl.isPrepared()).thenReturn(true); when(mImpl.getCurrentPosition()).thenReturn(0L); when(mImpl.getDuration()).thenReturn(20000L); when(mImpl.getBufferedPosition()).thenReturn(321L); mImpl.getCallback().onCurrentPositionChanged(mImpl); mImpl.getCallback().onDurationChanged(mImpl); mImpl.getCallback().onBufferedPositionChanged(mImpl); final Bitmap[] thumbs = new Bitmap[101]; for (int i = 0; i < 101; i++) { thumbs[i] = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888); } PlaybackSeekProviderSample provider = new PlaybackSeekProviderSample(10000L, 101) { @Override public void getThumbnail(int index, ResultCallback callback) { callback.onThumbnailLoaded(thumbs[index], index); } }; final long[] positions = provider.getSeekPositions(); mGlue.setSeekProvider(provider); mViewHolder.mProgressBar.requestFocus(); assertTrue(mViewHolder.mProgressBar.hasFocus()); sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT); verifyAtHeroIndex(positions, 1, thumbs); } }