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 17package androidx.leanback.widget; 18 19import static org.junit.Assert.assertEquals; 20import static org.junit.Assert.assertNull; 21import static org.junit.Assert.assertSame; 22import static org.junit.Assert.assertTrue; 23import static org.mockito.Matchers.any; 24import static org.mockito.Matchers.anyInt; 25import static org.mockito.Matchers.eq; 26import static org.mockito.Mockito.times; 27import static org.mockito.Mockito.verify; 28import static org.mockito.Mockito.when; 29 30import android.content.Context; 31import android.graphics.Bitmap; 32import android.graphics.drawable.ColorDrawable; 33import android.support.test.InstrumentationRegistry; 34import android.support.test.filters.SmallTest; 35import android.view.ContextThemeWrapper; 36import android.view.KeyEvent; 37import android.view.View; 38import android.view.ViewParent; 39 40import androidx.leanback.media.PlaybackTransportControlGlue; 41import androidx.leanback.media.PlayerAdapter; 42import androidx.leanback.widget.PlaybackSeekDataProvider.ResultCallback; 43 44import org.junit.Before; 45import org.junit.Test; 46import org.mockito.Mockito; 47 48import java.util.Arrays; 49 50@SmallTest 51public 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