Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.leanback.widget;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertNull;
     21 import static org.junit.Assert.assertSame;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.mockito.Matchers.any;
     24 import static org.mockito.Matchers.anyInt;
     25 import static org.mockito.Matchers.eq;
     26 import static org.mockito.Mockito.times;
     27 import static org.mockito.Mockito.verify;
     28 import static org.mockito.Mockito.when;
     29 
     30 import android.content.Context;
     31 import android.graphics.Bitmap;
     32 import android.graphics.drawable.ColorDrawable;
     33 import android.support.test.InstrumentationRegistry;
     34 import android.support.test.filters.SmallTest;
     35 import android.view.ContextThemeWrapper;
     36 import android.view.KeyEvent;
     37 import android.view.View;
     38 import android.view.ViewParent;
     39 
     40 import androidx.leanback.media.PlaybackTransportControlGlue;
     41 import androidx.leanback.media.PlayerAdapter;
     42 import androidx.leanback.widget.PlaybackSeekDataProvider.ResultCallback;
     43 
     44 import org.junit.Before;
     45 import org.junit.Test;
     46 import org.mockito.Mockito;
     47 
     48 import java.util.Arrays;
     49 
     50 @SmallTest
     51 public class PlaybackTransportRowPresenterTest {
     52 
     53     Context mContext;
     54     PlaybackTransportControlGlue mGlue;
     55     PlaybackGlueHostImplWithViewHolder mHost;
     56     PlayerAdapter mImpl;
     57     PlaybackTransportRowPresenter.ViewHolder mViewHolder;
     58     AbstractDetailsDescriptionPresenter.ViewHolder mDescriptionViewHolder;
     59     int mNumbThumbs;
     60 
     61     @Before
     62     public void setUp() {
     63         mContext = new ContextThemeWrapper(
     64                 InstrumentationRegistry.getInstrumentation().getTargetContext(),
     65                 androidx.leanback.test.R.style.Theme_Leanback);
     66         mHost = new PlaybackGlueHostImplWithViewHolder(mContext);
     67         mImpl = Mockito.mock(PlayerAdapter.class);
     68         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
     69             @Override
     70             public void run() {
     71                 mGlue = new PlaybackTransportControlGlue(mContext, mImpl) {
     72                     @Override
     73                     protected void onCreatePrimaryActions(ArrayObjectAdapter
     74                             primaryActionsAdapter) {
     75                         super.onCreatePrimaryActions(primaryActionsAdapter);
     76                         primaryActionsAdapter.add(
     77                                 new PlaybackControlsRow.ClosedCaptioningAction(mContext));
     78                     }
     79 
     80                     @Override
     81                     protected void onCreateSecondaryActions(ArrayObjectAdapter
     82                             secondaryActionsAdapter) {
     83                         secondaryActionsAdapter.add(
     84                                 new PlaybackControlsRow.HighQualityAction(mContext));
     85                         secondaryActionsAdapter.add(
     86                                 new PlaybackControlsRow.PictureInPictureAction(mContext));
     87                     }
     88                 };
     89                 mGlue.setHost(mHost);
     90 
     91             }
     92         });
     93         mViewHolder = (PlaybackTransportRowPresenter.ViewHolder) mHost.mViewHolder;
     94         mDescriptionViewHolder = (AbstractDetailsDescriptionPresenter.ViewHolder)
     95                 mViewHolder.mDescriptionViewHolder;
     96         mNumbThumbs = mViewHolder.mThumbsBar.getChildCount();
     97         assertTrue((mNumbThumbs & 1) != 0);
     98     }
     99 
    100     void sendKeyUIThread(int keyCode) {
    101         sendKeyUIThread(keyCode, 1);
    102     }
    103 
    104     void sendKeyUIThread(final int keyCode, final int repeat) {
    105         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    106             @Override
    107             public void run() {
    108                 mHost.sendKeyDownUp(keyCode, repeat);
    109             }
    110         });
    111     }
    112 
    113     void verifyGetThumbCalls(int firstHeroIndex, int lastHeroIndex,
    114             PlaybackSeekDataProvider provider, long[] positions) {
    115         int firstThumbIndex = Math.max(firstHeroIndex - (mNumbThumbs / 2), 0);
    116         int lastThumbIndex = Math.min(lastHeroIndex + (mNumbThumbs / 2), positions.length - 1);
    117         for (int i = firstThumbIndex; i <= lastThumbIndex; i++) {
    118             Mockito.verify(provider, times(1)).getThumbnail(eq(i), any(ResultCallback.class));
    119         }
    120         Mockito.verify(provider, times(0)).getThumbnail(
    121                 eq(firstThumbIndex - 1), any(ResultCallback.class));
    122         Mockito.verify(provider, times(0)).getThumbnail(
    123                 eq(firstThumbIndex - 2), any(ResultCallback.class));
    124         Mockito.verify(provider, times(0)).getThumbnail(
    125                 eq(lastThumbIndex + 1), any(ResultCallback.class));
    126         Mockito.verify(provider, times(0)).getThumbnail(
    127                 eq(lastThumbIndex + 2), any(ResultCallback.class));
    128     }
    129 
    130     void verifyAtHeroIndexWithDifferentPosition(long position, int heroIndex) {
    131         assertEquals(position, mGlue.getControlsRow().getCurrentPosition());
    132         assertEquals(mViewHolder.mThumbHeroIndex, heroIndex);
    133     }
    134 
    135     void verifyAtHeroIndex(long[] positions, int heroIndex) {
    136         verifyAtHeroIndex(positions, heroIndex, null);
    137     }
    138 
    139     void verifyAtHeroIndex(long[] positions, int heroIndex, Bitmap[] thumbs) {
    140         assertEquals(positions[heroIndex], mGlue.getControlsRow().getCurrentPosition());
    141         assertEquals(mViewHolder.mThumbHeroIndex, heroIndex);
    142         if (thumbs != null) {
    143             int start = Math.max(0, mViewHolder.mThumbHeroIndex - mNumbThumbs / 2);
    144             int end = Math.min(positions.length - 1, mViewHolder.mThumbHeroIndex + mNumbThumbs / 2);
    145             verifyThumbBitmaps(thumbs, start, end,
    146                     mViewHolder.mThumbsBar, start + mNumbThumbs / 2 - mViewHolder.mThumbHeroIndex,
    147                     end + mNumbThumbs / 2 - mViewHolder.mThumbHeroIndex);
    148         }
    149     }
    150 
    151     void verifyThumbBitmaps(Bitmap[] thumbs, int start, int end,
    152             ThumbsBar thumbsBar, int childStart, int childEnd) {
    153         assertEquals(end - start, childEnd - childStart);
    154         for (int i = start; i <= end; i++) {
    155             assertSame(thumbs[i], thumbsBar.getThumbBitmap(childStart + (i - start)));
    156         }
    157         for (int i = 0; i < childStart; i++) {
    158             assertNull(thumbsBar.getThumbBitmap(i));
    159         }
    160         for (int i = childEnd + 1; i < mNumbThumbs; i++) {
    161             assertNull(thumbsBar.getThumbBitmap(i));
    162         }
    163     }
    164 
    165     @Test
    166     public void progressUpdating() {
    167         when(mImpl.isPrepared()).thenReturn(true);
    168         when(mImpl.getCurrentPosition()).thenReturn(123L);
    169         when(mImpl.getDuration()).thenReturn(20000L);
    170         when(mImpl.getBufferedPosition()).thenReturn(321L);
    171 
    172         mGlue.play();
    173         Mockito.verify(mImpl, times(1)).play();
    174         mGlue.pause();
    175         Mockito.verify(mImpl, times(1)).pause();
    176         mGlue.seekTo(1231);
    177         Mockito.verify(mImpl, times(1)).seekTo(1231);
    178         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    179         mImpl.getCallback().onDurationChanged(mImpl);
    180         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    181         assertEquals(123L, mGlue.getCurrentPosition());
    182         assertEquals(20000L, mGlue.getDuration());
    183         assertEquals(321L, mGlue.getBufferedPosition());
    184         assertEquals(123L, mViewHolder.mCurrentTimeInMs);
    185         assertEquals(20000L, mViewHolder.mTotalTimeInMs);
    186         assertEquals(321L, mViewHolder.mSecondaryProgressInMs);
    187 
    188         when(mImpl.getCurrentPosition()).thenReturn(124L);
    189         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    190         assertEquals(124L, mGlue.getControlsRow().getCurrentPosition());
    191         assertEquals(124L, mViewHolder.mCurrentTimeInMs);
    192         when(mImpl.getBufferedPosition()).thenReturn(333L);
    193         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    194         assertEquals(333L, mGlue.getControlsRow().getBufferedPosition());
    195         assertEquals(333L, mViewHolder.mSecondaryProgressInMs);
    196         when(mImpl.getDuration()).thenReturn((long) (Integer.MAX_VALUE) * 2);
    197         mImpl.getCallback().onDurationChanged(mImpl);
    198         assertEquals((long) (Integer.MAX_VALUE) * 2, mGlue.getControlsRow().getDuration());
    199         assertEquals((long) (Integer.MAX_VALUE) * 2, mViewHolder.mTotalTimeInMs);
    200     }
    201 
    202     @Test
    203     public void mediaInfo() {
    204         final ColorDrawable art = new ColorDrawable();
    205         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    206             @Override
    207             public void run() {
    208                 mGlue.setTitle("xyz");
    209                 mGlue.setSubtitle("zyx");
    210                 mGlue.setArt(art);
    211             }
    212         });
    213         assertEquals("xyz", mDescriptionViewHolder.mTitle.getText());
    214         assertEquals("zyx", mDescriptionViewHolder.mSubtitle.getText());
    215         assertSame(art, mViewHolder.mImageView.getDrawable());
    216     }
    217 
    218     static boolean isDescendant(View view, View descendant) {
    219         while (descendant != view) {
    220             ViewParent p = descendant.getParent();
    221             if (!(p instanceof View)) {
    222                 return false;
    223             }
    224             descendant = (View) p;
    225         }
    226         return true;
    227     }
    228 
    229     @Test
    230     public void navigateRightInPrimary() {
    231         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    232             @Override
    233             public void run() {
    234                 mViewHolder.mControlsVh.mControlBar.getChildAt(0).requestFocus();
    235             }
    236         });
    237         View view = mViewHolder.view.findFocus();
    238         assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
    239         assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(1),
    240                 view.focusSearch(View.FOCUS_RIGHT)));
    241     }
    242 
    243     @Test
    244     public void navigateRightInSecondary() {
    245         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    246             @Override
    247             public void run() {
    248                 mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0).requestFocus();
    249             }
    250         });
    251         View view = mViewHolder.view.findFocus();
    252         assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
    253         assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(1),
    254                 view.focusSearch(View.FOCUS_RIGHT)));
    255     }
    256 
    257     @Test
    258     public void navigatePrimaryDownToProgress() {
    259         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    260             @Override
    261             public void run() {
    262                 mViewHolder.mControlsVh.mControlBar.getChildAt(0).requestFocus();
    263             }
    264         });
    265         View view = mViewHolder.view.findFocus();
    266         assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
    267         assertSame(mViewHolder.mProgressBar, view.focusSearch(View.FOCUS_DOWN));
    268     }
    269 
    270     @Test
    271     public void navigateProgressUpToPrimary() {
    272         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    273             @Override
    274             public void run() {
    275                 mViewHolder.mProgressBar.requestFocus();
    276             }
    277         });
    278         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    279             @Override
    280             public void run() {
    281                 mViewHolder.mProgressBar.focusSearch(View.FOCUS_UP).requestFocus();
    282             }
    283         });
    284         View view = mViewHolder.view.findFocus();
    285         assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
    286     }
    287 
    288     @Test
    289     public void navigateProgressDownToSecondary() {
    290         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    291             @Override
    292             public void run() {
    293                 mViewHolder.mProgressBar.requestFocus();
    294             }
    295         });
    296         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    297             @Override
    298             public void run() {
    299                 mViewHolder.mProgressBar.focusSearch(View.FOCUS_DOWN).requestFocus();
    300             }
    301         });
    302         View view = mViewHolder.view.findFocus();
    303         assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
    304     }
    305 
    306     @Test
    307     public void navigateSecondaryUpToProgress() {
    308         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    309             @Override
    310             public void run() {
    311                 mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0).requestFocus();
    312             }
    313         });
    314         View view = mViewHolder.view.findFocus();
    315         assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
    316         assertSame(mViewHolder.mProgressBar, view.focusSearch(View.FOCUS_UP));
    317     }
    318 
    319     @Test
    320     public void seekAndConfirm() {
    321         when(mImpl.isPrepared()).thenReturn(true);
    322         when(mImpl.getCurrentPosition()).thenReturn(0L);
    323         when(mImpl.getDuration()).thenReturn(20000L);
    324         when(mImpl.getBufferedPosition()).thenReturn(321L);
    325         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    326         mImpl.getCallback().onDurationChanged(mImpl);
    327         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    328 
    329         PlaybackSeekProviderSample provider = Mockito.spy(
    330                 new PlaybackSeekProviderSample(10000L, 101));
    331         final long[] positions = provider.getSeekPositions();
    332         mGlue.setSeekProvider(provider);
    333         mViewHolder.mProgressBar.requestFocus();
    334         assertTrue(mViewHolder.mProgressBar.hasFocus());
    335 
    336         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    337         verifyAtHeroIndex(positions, 1);
    338         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    339         verifyAtHeroIndex(positions, 2);
    340 
    341         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_CENTER);
    342         Mockito.verify(mImpl).seekTo(positions[2]);
    343 
    344         verifyGetThumbCalls(1, 2, provider, positions);
    345     }
    346 
    347     @Test
    348     public void playSeekToZero() {
    349         when(mImpl.isPrepared()).thenReturn(true);
    350         when(mImpl.getCurrentPosition()).thenReturn(0L);
    351         when(mImpl.getDuration()).thenReturn(20000L);
    352         when(mImpl.getBufferedPosition()).thenReturn(321L);
    353         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    354         mImpl.getCallback().onDurationChanged(mImpl);
    355         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    356 
    357         PlaybackSeekProviderSample provider = Mockito.spy(
    358                 new PlaybackSeekProviderSample(10000L, 101));
    359         final long[] positions = provider.getSeekPositions();
    360         mGlue.setSeekProvider(provider);
    361 
    362         // start play
    363         mGlue.play();
    364         verify(mImpl).play();
    365 
    366         // focus to seek bar
    367         mViewHolder.mProgressBar.requestFocus();
    368         assertTrue(mViewHolder.mProgressBar.hasFocus());
    369 
    370         // using DPAD_RIGHT to initiate seeking
    371         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    372         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    373         verifyAtHeroIndex(positions, 2);
    374         // press DPAD_CENTER to seek to new position and continue play
    375         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_CENTER);
    376         verify(mImpl).seekTo(positions[2]);
    377         verify(mImpl).play();
    378 
    379         // press DPAD_LEFT seek to 0
    380         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    381         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    382         verifyAtHeroIndex(positions, 0);
    383         // press DPAD_CENTER to continue play from 0
    384         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_CENTER);
    385         verify(mImpl).seekTo(0);
    386         verify(mImpl).play();
    387     }
    388 
    389     @Test
    390     public void playSeekAndCancel() {
    391         when(mImpl.isPrepared()).thenReturn(true);
    392         when(mImpl.getCurrentPosition()).thenReturn(0L);
    393         when(mImpl.getDuration()).thenReturn(20000L);
    394         when(mImpl.getBufferedPosition()).thenReturn(321L);
    395         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    396         mImpl.getCallback().onDurationChanged(mImpl);
    397         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    398 
    399         PlaybackSeekProviderSample provider = Mockito.spy(
    400                 new PlaybackSeekProviderSample(10000L, 101));
    401         final long[] positions = provider.getSeekPositions();
    402         mGlue.setSeekProvider(provider);
    403 
    404         // start play
    405         mGlue.play();
    406         verify(mImpl).play();
    407 
    408         // focus to seek bar
    409         mViewHolder.mProgressBar.requestFocus();
    410         assertTrue(mViewHolder.mProgressBar.hasFocus());
    411 
    412         // using DPAD_RIGHT to initiate seeking
    413         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    414         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    415         verifyAtHeroIndex(positions, 2);
    416         // press DPAD_CENTER to seek to new position and continue play
    417         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_CENTER);
    418         verify(mImpl).seekTo(positions[2]);
    419         verify(mImpl).play();
    420 
    421         // press DPAD_LEFT seek to 0
    422         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    423         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    424         verifyAtHeroIndex(positions, 0);
    425         // press BACK to cancel and continue play from position before seek
    426         sendKeyUIThread(KeyEvent.KEYCODE_BACK);
    427         verify(mImpl).seekTo(positions[2]);
    428         verify(mImpl).play();
    429     }
    430 
    431     @Test
    432     public void seekHoldKeyDown() {
    433         when(mImpl.isPrepared()).thenReturn(true);
    434         when(mImpl.getCurrentPosition()).thenReturn(4489L);
    435         when(mImpl.getDuration()).thenReturn(20000L);
    436         when(mImpl.getBufferedPosition()).thenReturn(4489L);
    437         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    438         mImpl.getCallback().onDurationChanged(mImpl);
    439         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    440 
    441         PlaybackSeekProviderSample provider = Mockito.spy(
    442                 new PlaybackSeekProviderSample(10000L, 101));
    443         final long[] positions = provider.getSeekPositions();
    444         mGlue.setSeekProvider(provider);
    445         mViewHolder.mProgressBar.requestFocus();
    446         assertTrue(mViewHolder.mProgressBar.hasFocus());
    447 
    448         int insertPosition = -1 - Arrays.binarySearch(positions, 4489L);
    449         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT, 5);
    450         verifyAtHeroIndex(positions, insertPosition + 4);
    451         verifyGetThumbCalls(insertPosition, insertPosition + 4, provider, positions);
    452 
    453         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT, 5);
    454         verifyAtHeroIndex(positions, insertPosition - 1);
    455     }
    456 
    457     @Test
    458     public void seekAndCancel() {
    459         when(mImpl.isPrepared()).thenReturn(true);
    460         when(mImpl.getCurrentPosition()).thenReturn(0L);
    461         when(mImpl.getDuration()).thenReturn(20000L);
    462         when(mImpl.getBufferedPosition()).thenReturn(321L);
    463         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    464         mImpl.getCallback().onDurationChanged(mImpl);
    465         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    466 
    467         PlaybackSeekProviderSample provider = Mockito.spy(
    468                 new PlaybackSeekProviderSample(10000L, 101));
    469         final long[] positions = provider.getSeekPositions();
    470         mGlue.setSeekProvider(provider);
    471         mViewHolder.mProgressBar.requestFocus();
    472         assertTrue(mViewHolder.mProgressBar.hasFocus());
    473 
    474         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    475         verifyAtHeroIndex(positions, 1);
    476 
    477         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    478         verifyAtHeroIndex(positions, 2);
    479 
    480         sendKeyUIThread(KeyEvent.KEYCODE_BACK);
    481         Mockito.verify(mImpl, times(0)).seekTo(anyInt());
    482         verifyGetThumbCalls(1, 2, provider, positions);
    483     }
    484 
    485     @Test
    486     public void seekUpBetweenTwoKeyPosition() {
    487         PlaybackSeekProviderSample provider = Mockito.spy(
    488                 new PlaybackSeekProviderSample(10000L, 101));
    489         final long[] positions = provider.getSeekPositions();
    490 
    491         // initially select between 0 and 1
    492         when(mImpl.isPrepared()).thenReturn(true);
    493         when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2);
    494         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    495         when(mImpl.getDuration()).thenReturn(20000L);
    496         when(mImpl.getBufferedPosition()).thenReturn(321L);
    497         mImpl.getCallback().onDurationChanged(mImpl);
    498         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    499 
    500         mGlue.setSeekProvider(provider);
    501         mViewHolder.mProgressBar.requestFocus();
    502         assertTrue(mViewHolder.mProgressBar.hasFocus());
    503 
    504         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    505         verifyAtHeroIndex(positions, 1);
    506         verifyGetThumbCalls(1, 1, provider, positions);
    507     }
    508 
    509     @Test
    510     public void seekDownBetweenTwoKeyPosition() {
    511         PlaybackSeekProviderSample provider = Mockito.spy(
    512                 new PlaybackSeekProviderSample(10000L, 101));
    513         final long[] positions = provider.getSeekPositions();
    514         assertTrue(positions[0] == 0);
    515 
    516         // initially select between 0 and 1
    517         when(mImpl.isPrepared()).thenReturn(true);
    518         when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2);
    519         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    520         when(mImpl.getDuration()).thenReturn(20000L);
    521         when(mImpl.getBufferedPosition()).thenReturn(321L);
    522         mImpl.getCallback().onDurationChanged(mImpl);
    523         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    524 
    525         mGlue.setSeekProvider(provider);
    526         mViewHolder.mProgressBar.requestFocus();
    527         assertTrue(mViewHolder.mProgressBar.hasFocus());
    528 
    529         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    530         verifyAtHeroIndex(positions, 0);
    531         verifyGetThumbCalls(0, 0, provider, positions);
    532     }
    533 
    534     @Test
    535     public void seekDownOutOfKeyPositions() {
    536         PlaybackSeekProviderSample provider = Mockito.spy(
    537                 new PlaybackSeekProviderSample(1000L, 10000L, 101));
    538         final long[] positions = provider.getSeekPositions();
    539         assertTrue(positions[0] > 0);
    540 
    541         // initially select between 0 and 1
    542         when(mImpl.isPrepared()).thenReturn(true);
    543         when(mImpl.getCurrentPosition()).thenReturn((positions[0] + positions[1]) / 2);
    544         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    545         when(mImpl.getDuration()).thenReturn(20000L);
    546         when(mImpl.getBufferedPosition()).thenReturn(321L);
    547         mImpl.getCallback().onDurationChanged(mImpl);
    548         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    549 
    550         mGlue.setSeekProvider(provider);
    551         mViewHolder.mProgressBar.requestFocus();
    552         assertTrue(mViewHolder.mProgressBar.hasFocus());
    553 
    554         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    555         verifyAtHeroIndex(positions, 0);
    556         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    557         verifyAtHeroIndexWithDifferentPosition(0, 0);
    558         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    559         verifyAtHeroIndexWithDifferentPosition(0, 0);
    560         verifyGetThumbCalls(0, 0, provider, positions);
    561     }
    562 
    563     @Test
    564     public void seekDownAheadOfKeyPositions() {
    565         PlaybackSeekProviderSample provider = Mockito.spy(
    566                 new PlaybackSeekProviderSample(1000L, 10000L, 101));
    567         final long[] positions = provider.getSeekPositions();
    568         assertTrue(positions[0] > 0);
    569 
    570         // initially select between 0 and 1
    571         when(mImpl.isPrepared()).thenReturn(true);
    572         when(mImpl.getCurrentPosition()).thenReturn(positions[0] / 2);
    573         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    574         when(mImpl.getDuration()).thenReturn(20000L);
    575         when(mImpl.getBufferedPosition()).thenReturn(321L);
    576         mImpl.getCallback().onDurationChanged(mImpl);
    577         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    578 
    579         mGlue.setSeekProvider(provider);
    580         mViewHolder.mProgressBar.requestFocus();
    581         assertTrue(mViewHolder.mProgressBar.hasFocus());
    582 
    583         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    584         verifyAtHeroIndexWithDifferentPosition(0, 0);
    585         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    586         verifyAtHeroIndex(positions, 0);
    587         verifyGetThumbCalls(0, 0, provider, positions);
    588     }
    589 
    590     @Test
    591     public void seekUpAheadOfKeyPositions() {
    592         PlaybackSeekProviderSample provider = Mockito.spy(
    593                 new PlaybackSeekProviderSample(1000L, 10000L, 101));
    594         final long[] positions = provider.getSeekPositions();
    595         assertTrue(positions[0] > 0);
    596 
    597         // initially select between 0 and 1
    598         when(mImpl.isPrepared()).thenReturn(true);
    599         when(mImpl.getCurrentPosition()).thenReturn(positions[0] / 2);
    600         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    601         when(mImpl.getDuration()).thenReturn(20000L);
    602         when(mImpl.getBufferedPosition()).thenReturn(321L);
    603         mImpl.getCallback().onDurationChanged(mImpl);
    604         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    605 
    606         mGlue.setSeekProvider(provider);
    607         mViewHolder.mProgressBar.requestFocus();
    608         assertTrue(mViewHolder.mProgressBar.hasFocus());
    609 
    610         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    611         verifyAtHeroIndex(positions, 0);
    612         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    613         verifyAtHeroIndexWithDifferentPosition(0, 0);
    614         verifyGetThumbCalls(0, 0, provider, positions);
    615     }
    616 
    617     @Test
    618     public void seekUpOutOfKeyPositions() {
    619         PlaybackSeekProviderSample provider = Mockito.spy(
    620                 new PlaybackSeekProviderSample(10000L, 101));
    621         final long[] positions = provider.getSeekPositions();
    622 
    623         // initially select between nth-1 and nth
    624         when(mImpl.isPrepared()).thenReturn(true);
    625         when(mImpl.getCurrentPosition()).thenReturn((positions[positions.length - 2]
    626                 + positions[positions.length - 1]) / 2);
    627         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    628         when(mImpl.getDuration()).thenReturn(20000L);
    629         when(mImpl.getBufferedPosition()).thenReturn(321L);
    630         mImpl.getCallback().onDurationChanged(mImpl);
    631         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    632 
    633         mGlue.setSeekProvider(provider);
    634         mViewHolder.mProgressBar.requestFocus();
    635         assertTrue(mViewHolder.mProgressBar.hasFocus());
    636 
    637         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    638         verifyAtHeroIndex(positions, positions.length - 1);
    639         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    640         verifyAtHeroIndex(positions, positions.length - 2);
    641         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    642         verifyAtHeroIndex(positions, positions.length - 1);
    643         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    644         verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1);
    645         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    646         verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1);
    647         verifyGetThumbCalls(positions.length - 2, positions.length - 1, provider, positions);
    648     }
    649 
    650     @Test
    651     public void seekUpAfterKeyPositions() {
    652         PlaybackSeekProviderSample provider = Mockito.spy(
    653                 new PlaybackSeekProviderSample(10000L, 101));
    654         final long[] positions = provider.getSeekPositions();
    655 
    656         // initially select after last item
    657         when(mImpl.isPrepared()).thenReturn(true);
    658         when(mImpl.getCurrentPosition()).thenReturn(positions[positions.length - 1] + 100);
    659         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    660         when(mImpl.getDuration()).thenReturn(20000L);
    661         when(mImpl.getBufferedPosition()).thenReturn(321L);
    662         mImpl.getCallback().onDurationChanged(mImpl);
    663         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    664 
    665         mGlue.setSeekProvider(provider);
    666         mViewHolder.mProgressBar.requestFocus();
    667         assertTrue(mViewHolder.mProgressBar.hasFocus());
    668 
    669         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    670         verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1);
    671         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    672         verifyAtHeroIndex(positions, positions.length - 1);
    673         verifyGetThumbCalls(positions.length - 1, positions.length - 1, provider, positions);
    674     }
    675 
    676     @Test
    677     public void seekDownAfterKeyPositions() {
    678         PlaybackSeekProviderSample provider = Mockito.spy(
    679                 new PlaybackSeekProviderSample(10000L, 101));
    680         final long[] positions = provider.getSeekPositions();
    681 
    682         // initially select after last item
    683         when(mImpl.isPrepared()).thenReturn(true);
    684         when(mImpl.getCurrentPosition()).thenReturn(positions[positions.length - 1] + 100);
    685         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    686         when(mImpl.getDuration()).thenReturn(20000L);
    687         when(mImpl.getBufferedPosition()).thenReturn(321L);
    688         mImpl.getCallback().onDurationChanged(mImpl);
    689         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    690 
    691         mGlue.setSeekProvider(provider);
    692         mViewHolder.mProgressBar.requestFocus();
    693         assertTrue(mViewHolder.mProgressBar.hasFocus());
    694 
    695         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_LEFT);
    696         verifyAtHeroIndex(positions, positions.length - 1);
    697         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    698         verifyAtHeroIndexWithDifferentPosition(20000L, positions.length - 1);
    699         verifyGetThumbCalls(positions.length - 1, positions.length - 1, provider, positions);
    700     }
    701 
    702     @Test
    703     public void thumbLoadedInCallback() {
    704         when(mImpl.isPrepared()).thenReturn(true);
    705         when(mImpl.getCurrentPosition()).thenReturn(0L);
    706         when(mImpl.getDuration()).thenReturn(20000L);
    707         when(mImpl.getBufferedPosition()).thenReturn(321L);
    708         mImpl.getCallback().onCurrentPositionChanged(mImpl);
    709         mImpl.getCallback().onDurationChanged(mImpl);
    710         mImpl.getCallback().onBufferedPositionChanged(mImpl);
    711 
    712         final Bitmap[] thumbs = new Bitmap[101];
    713         for (int i = 0; i < 101; i++) {
    714             thumbs[i] = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888);
    715         }
    716         PlaybackSeekProviderSample provider = new PlaybackSeekProviderSample(10000L, 101) {
    717             @Override
    718             public void getThumbnail(int index, ResultCallback callback) {
    719                 callback.onThumbnailLoaded(thumbs[index], index);
    720             }
    721         };
    722         final long[] positions = provider.getSeekPositions();
    723         mGlue.setSeekProvider(provider);
    724         mViewHolder.mProgressBar.requestFocus();
    725         assertTrue(mViewHolder.mProgressBar.hasFocus());
    726 
    727         sendKeyUIThread(KeyEvent.KEYCODE_DPAD_RIGHT);
    728         verifyAtHeroIndex(positions, 1, thumbs);
    729     }
    730 
    731 }
    732