1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.O; 4 import static com.google.common.truth.Truth.assertThat; 5 import static org.junit.Assert.fail; 6 import static org.robolectric.Shadows.shadowOf; 7 import static org.robolectric.shadows.ShadowMediaPlayer.State.END; 8 import static org.robolectric.shadows.ShadowMediaPlayer.State.ERROR; 9 import static org.robolectric.shadows.ShadowMediaPlayer.State.IDLE; 10 import static org.robolectric.shadows.ShadowMediaPlayer.State.INITIALIZED; 11 import static org.robolectric.shadows.ShadowMediaPlayer.State.PAUSED; 12 import static org.robolectric.shadows.ShadowMediaPlayer.State.PLAYBACK_COMPLETED; 13 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARED; 14 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARING; 15 import static org.robolectric.shadows.ShadowMediaPlayer.State.STARTED; 16 import static org.robolectric.shadows.ShadowMediaPlayer.State.STOPPED; 17 import static org.robolectric.shadows.ShadowMediaPlayer.addException; 18 import static org.robolectric.shadows.util.DataSource.toDataSource; 19 20 import android.app.Application; 21 import android.media.AudioManager; 22 import android.media.MediaPlayer; 23 import android.net.Uri; 24 import android.os.Looper; 25 import androidx.test.core.app.ApplicationProvider; 26 import androidx.test.ext.junit.runners.AndroidJUnit4; 27 import java.io.File; 28 import java.io.FileDescriptor; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.lang.reflect.InvocationTargetException; 32 import java.lang.reflect.Method; 33 import java.util.EnumSet; 34 import java.util.HashMap; 35 import java.util.Map; 36 import java.util.concurrent.ExecutionException; 37 import java.util.concurrent.Executors; 38 import java.util.concurrent.atomic.AtomicBoolean; 39 import org.junit.Before; 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.mockito.Mockito; 43 import org.robolectric.Robolectric; 44 import org.robolectric.annotation.Config; 45 import org.robolectric.shadow.api.Shadow; 46 import org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior; 47 import org.robolectric.shadows.ShadowMediaPlayer.MediaEvent; 48 import org.robolectric.shadows.ShadowMediaPlayer.MediaInfo; 49 import org.robolectric.shadows.ShadowMediaPlayer.State; 50 import org.robolectric.shadows.util.DataSource; 51 import org.robolectric.util.ReflectionHelpers; 52 import org.robolectric.util.Scheduler; 53 54 @RunWith(AndroidJUnit4.class) 55 public class ShadowMediaPlayerTest { 56 57 private static final String DUMMY_SOURCE = "dummy-source"; 58 59 private MediaPlayer mediaPlayer; 60 private ShadowMediaPlayer shadowMediaPlayer; 61 private MediaPlayer.OnCompletionListener completionListener; 62 private MediaPlayer.OnErrorListener errorListener; 63 private MediaPlayer.OnInfoListener infoListener; 64 private MediaPlayer.OnPreparedListener preparedListener; 65 private MediaPlayer.OnSeekCompleteListener seekListener; 66 private Scheduler scheduler; 67 private MediaInfo info; 68 private DataSource defaultSource; 69 70 @Before 71 public void setUp() { 72 mediaPlayer = Shadow.newInstanceOf(MediaPlayer.class); 73 shadowMediaPlayer = shadowOf(mediaPlayer); 74 75 completionListener = Mockito.mock(MediaPlayer.OnCompletionListener.class); 76 mediaPlayer.setOnCompletionListener(completionListener); 77 78 preparedListener = Mockito.mock(MediaPlayer.OnPreparedListener.class); 79 mediaPlayer.setOnPreparedListener(preparedListener); 80 81 errorListener = Mockito.mock(MediaPlayer.OnErrorListener.class); 82 mediaPlayer.setOnErrorListener(errorListener); 83 84 infoListener = Mockito.mock(MediaPlayer.OnInfoListener.class); 85 mediaPlayer.setOnInfoListener(infoListener); 86 87 seekListener = Mockito.mock(MediaPlayer.OnSeekCompleteListener.class); 88 mediaPlayer.setOnSeekCompleteListener(seekListener); 89 90 // Scheduler is used in many of the tests to simulate 91 // moving forward in time. 92 scheduler = Robolectric.getForegroundThreadScheduler(); 93 scheduler.pause(); 94 95 defaultSource = toDataSource(DUMMY_SOURCE); 96 info = new MediaInfo(); 97 ShadowMediaPlayer.addMediaInfo(defaultSource, info); 98 shadowMediaPlayer.doSetDataSource(defaultSource); 99 } 100 101 @Test 102 public void create_withResourceId_shouldSetDataSource() { 103 Application context = ApplicationProvider.getApplicationContext(); 104 ShadowMediaPlayer.addMediaInfo( 105 DataSource.toDataSource("android.resource://" + context.getPackageName() + "/123"), 106 new ShadowMediaPlayer.MediaInfo(100, 10)); 107 108 MediaPlayer mp = MediaPlayer.create(context, 123); 109 ShadowMediaPlayer shadow = shadowOf(mp); 110 assertThat(shadow.getDataSource()) 111 .isEqualTo( 112 DataSource.toDataSource("android.resource://" + context.getPackageName() + "/123")); 113 } 114 115 @Test 116 public void testInitialState() { 117 assertThat(shadowMediaPlayer.getState()).isEqualTo(IDLE); 118 } 119 120 @Test 121 public void testCreateListener() { 122 ShadowMediaPlayer.CreateListener createListener = Mockito 123 .mock(ShadowMediaPlayer.CreateListener.class); 124 ShadowMediaPlayer.setCreateListener(createListener); 125 126 MediaPlayer newPlayer = new MediaPlayer(); 127 ShadowMediaPlayer shadow = shadowOf(newPlayer); 128 129 Mockito.verify(createListener).onCreate(newPlayer, shadow); 130 } 131 132 @Test 133 public void testResetResetsPosition() { 134 shadowMediaPlayer.setCurrentPosition(300); 135 mediaPlayer.reset(); 136 assertThat(shadowMediaPlayer.getCurrentPositionRaw()) 137 .isEqualTo(0); 138 } 139 140 @Test 141 public void testPrepare() throws IOException { 142 int[] testDelays = { 0, 10, 100, 1500 }; 143 144 for (int delay : testDelays) { 145 final long startTime = scheduler.getCurrentTime(); 146 info.setPreparationDelay(delay); 147 shadowMediaPlayer.setState(INITIALIZED); 148 mediaPlayer.prepare(); 149 150 assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED); 151 assertThat(scheduler.getCurrentTime()).isEqualTo(startTime + delay); 152 } 153 } 154 155 @Test 156 public void testSetDataSourceString() throws IOException { 157 DataSource ds = toDataSource("dummy"); 158 ShadowMediaPlayer.addMediaInfo(ds, info); 159 mediaPlayer.setDataSource("dummy"); 160 assertThat(shadowMediaPlayer.getDataSource()).named("dataSource").isEqualTo(ds); 161 } 162 163 @Test 164 public void testSetDataSourceUri() throws IOException { 165 Map<String, String> headers = new HashMap<>(); 166 Uri uri = Uri.parse("file:/test"); 167 DataSource ds = toDataSource(ApplicationProvider.getApplicationContext(), uri, headers); 168 ShadowMediaPlayer.addMediaInfo(ds, info); 169 170 mediaPlayer.setDataSource(ApplicationProvider.getApplicationContext(), uri, headers); 171 172 assertThat(shadowMediaPlayer.getSourceUri()).named("sourceUri").isSameAs(uri); 173 assertThat(shadowMediaPlayer.getDataSource()).named("dataSource").isEqualTo(ds); 174 } 175 176 @Test 177 public void testSetDataSourceFD() throws IOException { 178 File tmpFile = File.createTempFile("MediaPlayerTest", null); 179 try { 180 tmpFile.deleteOnExit(); 181 FileInputStream is = new FileInputStream(tmpFile); 182 try { 183 FileDescriptor fd = is.getFD(); 184 DataSource ds = toDataSource(fd, 23, 524); 185 ShadowMediaPlayer.addMediaInfo(ds, info); 186 mediaPlayer.setDataSource(fd, 23, 524); 187 assertThat(shadowMediaPlayer.getSourceUri()).named("sourceUri").isNull(); 188 assertThat(shadowMediaPlayer.getDataSource()).named("dataSource") 189 .isEqualTo(ds); 190 } finally { 191 is.close(); 192 } 193 } finally { 194 tmpFile.delete(); 195 } 196 } 197 198 @Test 199 public void testPrepareAsyncAutoCallback() { 200 mediaPlayer.setOnPreparedListener(preparedListener); 201 int[] testDelays = { 0, 10, 100, 1500 }; 202 203 for (int delay : testDelays) { 204 info.setPreparationDelay(delay); 205 shadowMediaPlayer.setState(INITIALIZED); 206 final long startTime = scheduler.getCurrentTime(); 207 mediaPlayer.prepareAsync(); 208 209 assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARING); 210 Mockito.verifyZeroInteractions(preparedListener); 211 scheduler.advanceToLastPostedRunnable(); 212 assertThat(scheduler.getCurrentTime()).named("currentTime").isEqualTo( 213 startTime + delay); 214 assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED); 215 Mockito.verify(preparedListener).onPrepared(mediaPlayer); 216 Mockito.verifyNoMoreInteractions(preparedListener); 217 Mockito.reset(preparedListener); 218 } 219 } 220 221 @Test 222 public void testPrepareAsyncManualCallback() { 223 mediaPlayer.setOnPreparedListener(preparedListener); 224 info.setPreparationDelay(-1); 225 226 shadowMediaPlayer.setState(INITIALIZED); 227 final long startTime = scheduler.getCurrentTime(); 228 mediaPlayer.prepareAsync(); 229 230 assertThat(scheduler.getCurrentTime()).named("currentTime").isEqualTo( 231 startTime); 232 assertThat(shadowMediaPlayer.getState()).isSameAs(PREPARING); 233 Mockito.verifyZeroInteractions(preparedListener); 234 shadowMediaPlayer.invokePreparedListener(); 235 assertThat(shadowMediaPlayer.getState()).isSameAs(PREPARED); 236 Mockito.verify(preparedListener).onPrepared(mediaPlayer); 237 Mockito.verifyNoMoreInteractions(preparedListener); 238 } 239 240 @Test 241 public void testDefaultPreparationDelay() { 242 assertThat(info.getPreparationDelay()) 243 .named("preparationDelay").isEqualTo(0); 244 } 245 246 @Test 247 public void testIsPlaying() { 248 EnumSet<State> nonPlayingStates = EnumSet.of(IDLE, INITIALIZED, PREPARED, 249 PAUSED, STOPPED, PLAYBACK_COMPLETED); 250 for (State state : nonPlayingStates) { 251 shadowMediaPlayer.setState(state); 252 assertThat(mediaPlayer.isPlaying()).isFalse(); 253 } 254 shadowMediaPlayer.setState(STARTED); 255 assertThat(mediaPlayer.isPlaying()).isTrue(); 256 } 257 258 @Test 259 public void testIsPrepared() { 260 EnumSet<State> prepStates = EnumSet.of(PREPARED, STARTED, PAUSED, 261 PLAYBACK_COMPLETED); 262 263 for (State state : State.values()) { 264 shadowMediaPlayer.setState(state); 265 if (prepStates.contains(state)) { 266 assertThat(shadowMediaPlayer.isPrepared()).isTrue(); 267 } else { 268 assertThat(shadowMediaPlayer.isPrepared()).isFalse(); 269 } 270 } 271 } 272 273 @Test 274 public void testPlaybackProgress() { 275 shadowMediaPlayer.setState(PREPARED); 276 // This time offset is just to make sure that it doesn't work by 277 // accident because the offsets are calculated relative to 0. 278 scheduler.advanceBy(100); 279 280 mediaPlayer.start(); 281 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(0); 282 assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED); 283 284 scheduler.advanceBy(500); 285 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(500); 286 assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED); 287 288 scheduler.advanceBy(499); 289 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(999); 290 assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED); 291 Mockito.verifyZeroInteractions(completionListener); 292 293 scheduler.advanceBy(1); 294 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(1000); 295 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 296 Mockito.verify(completionListener).onCompletion(mediaPlayer); 297 Mockito.verifyNoMoreInteractions(completionListener); 298 299 scheduler.advanceBy(1); 300 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(1000); 301 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 302 Mockito.verifyZeroInteractions(completionListener); 303 } 304 305 @Test 306 public void testStop() { 307 shadowMediaPlayer.setState(PREPARED); 308 mediaPlayer.start(); 309 scheduler.advanceBy(300); 310 311 mediaPlayer.stop(); 312 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300); 313 314 scheduler.advanceBy(400); 315 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300); 316 } 317 318 @Test 319 public void testPauseReschedulesCompletionCallback() { 320 shadowMediaPlayer.setState(PREPARED); 321 mediaPlayer.start(); 322 scheduler.advanceBy(200); 323 mediaPlayer.pause(); 324 scheduler.advanceBy(800); 325 326 Mockito.verifyZeroInteractions(completionListener); 327 328 mediaPlayer.start(); 329 scheduler.advanceBy(799); 330 Mockito.verifyZeroInteractions(completionListener); 331 332 scheduler.advanceBy(1); 333 Mockito.verify(completionListener).onCompletion(mediaPlayer); 334 Mockito.verifyNoMoreInteractions(completionListener); 335 336 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 337 Mockito.verifyZeroInteractions(completionListener); 338 } 339 340 @Test 341 public void testPauseUpdatesPosition() { 342 shadowMediaPlayer.setState(PREPARED); 343 mediaPlayer.start(); 344 345 scheduler.advanceBy(200); 346 mediaPlayer.pause(); 347 scheduler.advanceBy(200); 348 349 assertThat(shadowMediaPlayer.getState()).isEqualTo(PAUSED); 350 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(200); 351 352 mediaPlayer.start(); 353 scheduler.advanceBy(200); 354 355 assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED); 356 assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(400); 357 } 358 359 @Test 360 public void testSeekDuringPlaybackReschedulesCompletionCallback() { 361 shadowMediaPlayer.setState(PREPARED); 362 mediaPlayer.start(); 363 364 scheduler.advanceBy(300); 365 mediaPlayer.seekTo(400); 366 scheduler.advanceBy(599); 367 Mockito.verifyZeroInteractions(completionListener); 368 scheduler.advanceBy(1); 369 Mockito.verify(completionListener).onCompletion(mediaPlayer); 370 Mockito.verifyNoMoreInteractions(completionListener); 371 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 372 373 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 374 Mockito.verifyZeroInteractions(completionListener); 375 } 376 377 @Test 378 public void testSeekDuringPlaybackUpdatesPosition() { 379 shadowMediaPlayer.setState(PREPARED); 380 381 // This time offset is just to make sure that it doesn't work by 382 // accident because the offsets are calculated relative to 0. 383 scheduler.advanceBy(100); 384 385 mediaPlayer.start(); 386 387 scheduler.advanceBy(400); 388 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(400); 389 390 mediaPlayer.seekTo(600); 391 scheduler.advanceBy(0); 392 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600); 393 394 scheduler.advanceBy(300); 395 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(900); 396 397 mediaPlayer.seekTo(100); 398 scheduler.advanceBy(0); 399 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(100); 400 401 scheduler.advanceBy(900); 402 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000); 403 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 404 405 scheduler.advanceBy(100); 406 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000); 407 } 408 409 @Config(minSdk = O) 410 @Test 411 public void testSeekToMode() { 412 shadowMediaPlayer.setState(PREPARED); 413 414 // This time offset is just to make sure that it doesn't work by 415 // accident because the offsets are calculated relative to 0. 416 scheduler.advanceBy(100); 417 418 mediaPlayer.start(); 419 420 scheduler.advanceBy(400); 421 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(400); 422 423 mediaPlayer.seekTo(600, MediaPlayer.SEEK_CLOSEST); 424 scheduler.advanceBy(0); 425 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600); 426 } 427 428 @Test 429 public void testPendingEventsRemovedOnError() { 430 Mockito.when(errorListener.onError(mediaPlayer, 2, 3)).thenReturn(true); 431 shadowMediaPlayer.setState(PREPARED); 432 mediaPlayer.start(); 433 scheduler.advanceBy(200); 434 435 // We should have a pending completion callback. 436 assertThat(scheduler.size()).isEqualTo(1); 437 438 shadowMediaPlayer.invokeErrorListener(2, 3); 439 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 440 Mockito.verifyZeroInteractions(completionListener); 441 } 442 443 @Test 444 public void testAttachAuxEffectStates() { 445 testStates(new MethodSpec("attachAuxEffect", 37), EnumSet.of(IDLE, ERROR), 446 onErrorTester, null); 447 } 448 449 private static final EnumSet<State> emptyStateSet = EnumSet 450 .noneOf(State.class); 451 452 @Test 453 public void testGetAudioSessionIdStates() { 454 testStates("getAudioSessionId", emptyStateSet, onErrorTester, null); 455 } 456 457 @Test 458 public void testGetCurrentPositionStates() { 459 testStates("getCurrentPosition", EnumSet.of(IDLE, ERROR), onErrorTester, 460 null); 461 } 462 463 @Test 464 public void testGetDurationStates() { 465 testStates("getDuration", EnumSet.of(IDLE, INITIALIZED, ERROR), 466 onErrorTester, null); 467 } 468 469 @Test 470 public void testGetVideoHeightAndWidthStates() { 471 testStates("getVideoHeight", EnumSet.of(IDLE, ERROR), logTester, null); 472 testStates("getVideoWidth", EnumSet.of(IDLE, ERROR), logTester, null); 473 } 474 475 @Test 476 public void testIsLoopingStates() { 477 // isLooping is quite unique as it throws ISE when in END state, 478 // even though every other state is legal. 479 testStates("isLooping", EnumSet.of(END), iseTester, null); 480 } 481 482 @Test 483 public void testIsPlayingStates() { 484 testStates("isPlaying", EnumSet.of(ERROR), onErrorTester, null); 485 } 486 487 @Test 488 public void testPauseStates() { 489 testStates("pause", 490 EnumSet.of(IDLE, INITIALIZED, PREPARED, STOPPED, ERROR), onErrorTester, 491 PAUSED); 492 } 493 494 @Test 495 public void testPrepareStates() { 496 testStates("prepare", 497 EnumSet.of(IDLE, PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR), 498 PREPARED); 499 } 500 501 @Test 502 public void testPrepareAsyncStates() { 503 testStates("prepareAsync", 504 EnumSet.of(IDLE, PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR), 505 PREPARING); 506 } 507 508 @Test 509 public void testReleaseStates() { 510 testStates("release", emptyStateSet, END); 511 } 512 513 @Test 514 public void testResetStates() { 515 testStates("reset", EnumSet.of(END), IDLE); 516 } 517 518 @Test 519 public void testSeekToStates() { 520 testStates(new MethodSpec("seekTo", 38), 521 EnumSet.of(IDLE, INITIALIZED, STOPPED, ERROR), onErrorTester, null); 522 } 523 524 @Test 525 public void testSetAudioSessionIdStates() { 526 testStates(new MethodSpec("setAudioSessionId", 40), EnumSet.of(INITIALIZED, 527 PREPARED, STARTED, PAUSED, STOPPED, PLAYBACK_COMPLETED, ERROR), 528 onErrorTester, null); 529 } 530 531 // NOTE: This test diverges from the spec in the MediaPlayer 532 // doc, which says that setAudioStreamType() is valid to call 533 // from any state other than ERROR. It mentions that 534 // unless you call it before prepare it won't be effective. 535 // However, by inspection I found that it actually calls onError 536 // and moves into the ERROR state unless invoked from IDLE state, 537 // so that is what I have emulated. 538 @Test 539 public void testSetAudioStreamTypeStates() { 540 testStates(new MethodSpec("setAudioStreamType", AudioManager.STREAM_MUSIC), 541 EnumSet.of(PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR), 542 onErrorTester, null); 543 } 544 545 @Test 546 public void testSetLoopingStates() { 547 testStates(new MethodSpec("setLooping", true), EnumSet.of(ERROR), 548 onErrorTester, null); 549 } 550 551 @Test 552 public void testSetVolumeStates() { 553 testStates(new MethodSpec("setVolume", new Class<?>[] { float.class, 554 float.class }, new Object[] { 1.0f, 1.0f }), EnumSet.of(ERROR), 555 onErrorTester, null); 556 } 557 558 @Test 559 public void testSetDataSourceStates() { 560 final EnumSet<State> invalidStates = EnumSet.of(INITIALIZED, PREPARED, 561 STARTED, PAUSED, PLAYBACK_COMPLETED, STOPPED, ERROR); 562 563 testStates(new MethodSpec("setDataSource", DUMMY_SOURCE), invalidStates, iseTester, INITIALIZED); 564 } 565 566 @Test 567 public void testStartStates() { 568 testStates("start", 569 EnumSet.of(IDLE, INITIALIZED, PREPARING, STOPPED, ERROR), 570 onErrorTester, STARTED); 571 } 572 573 @Test 574 public void testStopStates() { 575 testStates("stop", EnumSet.of(IDLE, INITIALIZED, ERROR), onErrorTester, 576 STOPPED); 577 } 578 579 @Test 580 public void testCurrentPosition() { 581 int[] positions = { 0, 1, 2, 1024 }; 582 for (int position : positions) { 583 shadowMediaPlayer.setCurrentPosition(position); 584 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(position); 585 } 586 } 587 588 @Test 589 public void testInitialAudioSessionIdIsNotZero() { 590 assertThat(mediaPlayer.getAudioSessionId()).named("initial audioSessionId") 591 .isNotEqualTo(0); 592 } 593 594 private Tester onErrorTester = new OnErrorTester(-38, 0); 595 private Tester iseTester = new ExceptionTester(IllegalStateException.class); 596 private Tester logTester = new LogTester(null); 597 private Tester assertTester = new ExceptionTester(AssertionError.class); 598 599 private void testStates(String methodName, EnumSet<State> invalidStates, 600 State nextState) { 601 testStates(new MethodSpec(methodName), invalidStates, iseTester, nextState); 602 } 603 604 public class MethodSpec { 605 public Method method; 606 // public String method; 607 public Class<?>[] argTypes; 608 public Object[] args; 609 610 public MethodSpec(String method) { 611 this(method, (Class<?>[]) null, (Object[]) null); 612 } 613 614 public MethodSpec(String method, Class<?>[] argTypes, Object[] args) { 615 try { 616 this.method = MediaPlayer.class.getDeclaredMethod(method, argTypes); 617 this.args = args; 618 } catch (NoSuchMethodException e) { 619 throw new AssertionError("Method lookup failed: " + method, e); 620 } 621 } 622 623 public MethodSpec(String method, int arg) { 624 this(method, new Class<?>[] { int.class }, new Object[] { arg }); 625 } 626 627 public MethodSpec(String method, boolean arg) { 628 this(method, new Class<?>[] { boolean.class }, new Object[] { arg }); 629 } 630 631 public MethodSpec(String method, Class<?> c) { 632 this(method, new Class<?>[] { c }, new Object[] { null }); 633 } 634 635 public MethodSpec(String method, Object o) { 636 this(method, new Class<?>[] { o.getClass() }, new Object[] { o }); 637 } 638 639 public <T> MethodSpec(String method, T o, Class<T> c) { 640 this(method, new Class<?>[] { c }, new Object[] { o }); 641 } 642 643 public void invoke() throws InvocationTargetException { 644 try { 645 method.invoke(mediaPlayer, args); 646 } catch (IllegalAccessException e) { 647 throw new AssertionError(e); 648 } 649 } 650 651 @Override public String toString() { 652 return method.toString(); 653 } 654 } 655 656 private void testStates(String method, EnumSet<State> invalidStates, 657 Tester tester, State next) { 658 testStates(new MethodSpec(method), invalidStates, tester, next); 659 } 660 661 private void testStates(MethodSpec method, EnumSet<State> invalidStates, 662 Tester tester, State next) { 663 final EnumSet<State> invalid = EnumSet.copyOf(invalidStates); 664 665 // The documentation specifies that the behavior of calling any 666 // function while in the PREPARING state is undefined. I tried 667 // to play it safe but reasonable, by looking at whether the PREPARED or 668 // INITIALIZED are allowed (ie, the two states that PREPARING 669 // sites between). Only if both these states are allowed is 670 // PREPARING allowed too, if either PREPARED or INITALIZED is 671 // disallowed then so is PREPARING. 672 if (invalid.contains(PREPARED) || invalid.contains(INITIALIZED)) { 673 invalid.add(PREPARING); 674 } 675 shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.SILENT); 676 for (State state : State.values()) { 677 shadowMediaPlayer.setState(state); 678 testMethodSuccess(method, next); 679 } 680 681 shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.EMULATE); 682 for (State state : invalid) { 683 shadowMediaPlayer.setState(state); 684 tester.test(method); 685 } 686 for (State state : EnumSet.complementOf(invalid)) { 687 if (state == END) { 688 continue; 689 } 690 shadowMediaPlayer.setState(state); 691 testMethodSuccess(method, next); 692 } 693 694 // END state: by inspection we determined that if a method 695 // doesn't raise any kind of error in any other state then neither 696 // will it raise one in the END state; however if it raises errors 697 // in other states of any kind then it will throw 698 // IllegalArgumentException when in END. 699 shadowMediaPlayer.setState(END); 700 if (invalid.isEmpty()) { 701 testMethodSuccess(method, END); 702 } else { 703 iseTester.test(method); 704 } 705 706 shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.ASSERT); 707 for (State state : invalid) { 708 shadowMediaPlayer.setState(state); 709 assertTester.test(method); 710 } 711 for (State state : EnumSet.complementOf(invalid)) { 712 if (state == END) { 713 continue; 714 } 715 shadowMediaPlayer.setState(state); 716 testMethodSuccess(method, next); 717 } 718 shadowMediaPlayer.setState(END); 719 if (invalid.isEmpty()) { 720 testMethodSuccess(method, END); 721 } else { 722 assertTester.test(method); 723 } 724 } 725 726 private interface Tester { 727 void test(MethodSpec method); 728 } 729 730 private class OnErrorTester implements Tester { 731 private int what; 732 private int extra; 733 734 public OnErrorTester(int what, int extra) { 735 this.what = what; 736 this.extra = extra; 737 } 738 739 @Override 740 public void test(MethodSpec method) { 741 final State state = shadowMediaPlayer.getState(); 742 final boolean wasPaused = scheduler.isPaused(); 743 scheduler.pause(); 744 try { 745 method.invoke(); 746 } catch (InvocationTargetException e) { 747 throw new RuntimeException("Expected <" + method 748 + "> to call onError rather than throw <" + e.getTargetException() 749 + "> when called from <" + state + ">", e); 750 } 751 Mockito.verifyZeroInteractions(errorListener); 752 final State finalState = shadowMediaPlayer.getState(); 753 assertThat(finalState).isSameAs(ERROR); 754 scheduler.unPause(); 755 Mockito.verify(errorListener).onError(mediaPlayer, what, extra); 756 Mockito.reset(errorListener); 757 if (wasPaused) { 758 scheduler.pause(); 759 } 760 } 761 } 762 763 private class ExceptionTester implements Tester { 764 private Class<? extends Throwable> eClass; 765 766 public ExceptionTester(Class<? extends Throwable> eClass) { 767 this.eClass = eClass; 768 } 769 770 @Override 771 @SuppressWarnings("MissingFail") 772 public void test(MethodSpec method) { 773 final State state = shadowMediaPlayer.getState(); 774 boolean success = false; 775 try { 776 method.invoke(); 777 success = true; 778 } catch (InvocationTargetException e) { 779 Throwable cause = e.getTargetException(); 780 assertThat(cause).isInstanceOf(eClass); 781 final State finalState = shadowMediaPlayer.getState(); 782 assertThat(finalState).isSameAs(state); 783 } 784 assertThat(success).isFalse(); 785 } 786 } 787 788 private class LogTester implements Tester { 789 private State next; 790 791 public LogTester(State next) { 792 this.next = next; 793 } 794 795 @Override 796 public void test(MethodSpec method) { 797 testMethodSuccess(method, next); 798 } 799 } 800 801 private void testMethodSuccess(MethodSpec method, State next) { 802 final State state = shadowMediaPlayer.getState(); 803 try { 804 method.invoke(); 805 final State finalState = shadowMediaPlayer.getState(); 806 if (next == null) { 807 assertThat(finalState).isEqualTo(state); 808 } else { 809 assertThat(finalState).isEqualTo(next); 810 } 811 } catch (InvocationTargetException e) { 812 Throwable cause = e.getTargetException(); 813 fail("<" + method + "> should not throw exception when in state <" 814 + state + ">" + cause); 815 } 816 } 817 818 private static final State[] seekableStates = { PREPARED, PAUSED, 819 PLAYBACK_COMPLETED, STARTED }; 820 821 // It is not 100% clear from the docs if seeking to < 0 should 822 // invoke an error. I have assumed from the documentation 823 // which says "Successful invoke of this method in a valid 824 // state does not change the state" that it doesn't invoke an 825 // error. Rounding the seek up to 0 seems to be the sensible 826 // alternative behavior. 827 @Test 828 public void testSeekBeforeStart() { 829 shadowMediaPlayer.setSeekDelay(-1); 830 for (State state : seekableStates) { 831 shadowMediaPlayer.setState(state); 832 shadowMediaPlayer.setCurrentPosition(500); 833 834 mediaPlayer.seekTo(-1); 835 shadowMediaPlayer.invokeSeekCompleteListener(); 836 837 assertThat(mediaPlayer.getCurrentPosition()).named( 838 "Current postion while " + state).isEqualTo(0); 839 assertThat(shadowMediaPlayer.getState()).named("Final state " + state) 840 .isEqualTo(state); 841 } 842 } 843 844 // Similar comments apply to this test as to 845 // testSeekBeforeStart(). 846 @Test 847 public void testSeekPastEnd() { 848 shadowMediaPlayer.setSeekDelay(-1); 849 for (State state : seekableStates) { 850 shadowMediaPlayer.setState(state); 851 shadowMediaPlayer.setCurrentPosition(500); 852 mediaPlayer.seekTo(1001); 853 shadowMediaPlayer.invokeSeekCompleteListener(); 854 855 assertThat(mediaPlayer.getCurrentPosition()).named( 856 "Current postion while " + state).isEqualTo(1000); 857 assertThat(shadowMediaPlayer.getState()).named("Final state " + state) 858 .isEqualTo(state); 859 } 860 } 861 862 @Test 863 public void testCompletionListener() { 864 shadowMediaPlayer.invokeCompletionListener(); 865 866 Mockito.verify(completionListener).onCompletion(mediaPlayer); 867 } 868 869 @Test 870 public void testCompletionWithoutListenerDoesNotThrowException() { 871 mediaPlayer.setOnCompletionListener(null); 872 shadowMediaPlayer.invokeCompletionListener(); 873 874 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 875 Mockito.verifyZeroInteractions(completionListener); 876 } 877 878 @Test 879 public void testSeekListener() { 880 shadowMediaPlayer.invokeSeekCompleteListener(); 881 882 Mockito.verify(seekListener).onSeekComplete(mediaPlayer); 883 } 884 885 @Test 886 public void testSeekWithoutListenerDoesNotThrowException() { 887 mediaPlayer.setOnSeekCompleteListener(null); 888 shadowMediaPlayer.invokeSeekCompleteListener(); 889 890 Mockito.verifyZeroInteractions(seekListener); 891 } 892 893 @Test 894 public void testSeekDuringPlaybackDelayedCallback() { 895 shadowMediaPlayer.setState(PREPARED); 896 shadowMediaPlayer.setSeekDelay(100); 897 898 assertThat(shadowMediaPlayer.getSeekDelay()).isEqualTo(100); 899 900 mediaPlayer.start(); 901 scheduler.advanceBy(200); 902 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200); 903 mediaPlayer.seekTo(450); 904 905 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200); 906 907 scheduler.advanceBy(99); 908 909 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200); 910 Mockito.verifyZeroInteractions(seekListener); 911 912 scheduler.advanceBy(1); 913 914 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(450); 915 Mockito.verify(seekListener).onSeekComplete(mediaPlayer); 916 917 assertThat(scheduler.advanceToLastPostedRunnable()).isTrue(); 918 Mockito.verifyNoMoreInteractions(seekListener); 919 } 920 921 @Test 922 public void testSeekWhilePausedDelayedCallback() { 923 shadowMediaPlayer.setState(PAUSED); 924 shadowMediaPlayer.setSeekDelay(100); 925 926 scheduler.advanceBy(200); 927 mediaPlayer.seekTo(450); 928 scheduler.advanceBy(99); 929 930 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0); 931 Mockito.verifyZeroInteractions(seekListener); 932 933 scheduler.advanceBy(1); 934 935 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(450); 936 Mockito.verify(seekListener).onSeekComplete(mediaPlayer); 937 // Check that no completion callback or alternative 938 // seek callbacks have been scheduled. 939 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 940 } 941 942 @Test 943 public void testSeekWhileSeekingWhilePaused() { 944 shadowMediaPlayer.setState(PAUSED); 945 shadowMediaPlayer.setSeekDelay(100); 946 947 scheduler.advanceBy(200); 948 mediaPlayer.seekTo(450); 949 scheduler.advanceBy(50); 950 mediaPlayer.seekTo(600); 951 scheduler.advanceBy(99); 952 953 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0); 954 Mockito.verifyZeroInteractions(seekListener); 955 956 scheduler.advanceBy(1); 957 958 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600); 959 Mockito.verify(seekListener).onSeekComplete(mediaPlayer); 960 // Check that no completion callback or alternative 961 // seek callbacks have been scheduled. 962 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 963 } 964 965 @Test 966 public void testSeekWhileSeekingWhilePlaying() { 967 shadowMediaPlayer.setState(PREPARED); 968 shadowMediaPlayer.setSeekDelay(100); 969 970 final long startTime = scheduler.getCurrentTime(); 971 mediaPlayer.start(); 972 scheduler.advanceBy(200); 973 mediaPlayer.seekTo(450); 974 scheduler.advanceBy(50); 975 mediaPlayer.seekTo(600); 976 scheduler.advanceBy(99); 977 978 // Not sure of the correct behavior to emulate here, as the MediaPlayer 979 // documentation is not detailed enough. There are three possibilities: 980 // 1. Playback is paused for the entire time that a seek is in progress. 981 // 2. Playback continues normally until the seek is complete. 982 // 3. Somewhere between these two extremes - playback continues for 983 // a while and then pauses until the seek is complete. 984 // I have decided to emulate the first. I don't think that 985 // implementations should depend on any of these particular behaviors 986 // and consider the behavior indeterminate. 987 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200); 988 Mockito.verifyZeroInteractions(seekListener); 989 990 scheduler.advanceBy(1); 991 992 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600); 993 Mockito.verify(seekListener).onSeekComplete(mediaPlayer); 994 // Check that the completion callback is scheduled properly 995 // but no alternative seek callbacks. 996 assertThat(scheduler.advanceToLastPostedRunnable()).isTrue(); 997 Mockito.verify(completionListener).onCompletion(mediaPlayer); 998 Mockito.verifyNoMoreInteractions(seekListener); 999 assertThat(scheduler.getCurrentTime()).isEqualTo(startTime + 750); 1000 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000); 1001 assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED); 1002 } 1003 1004 @Test 1005 public void testSimulatenousEventsAllRun() { 1006 // Simultaneous events should all run even if 1007 // one of them stops playback. 1008 MediaEvent e1 = new MediaEvent() { 1009 @Override 1010 public void run(MediaPlayer mp, ShadowMediaPlayer smp) { 1011 smp.doStop(); 1012 } 1013 }; 1014 MediaEvent e2 = Mockito.mock(MediaEvent.class); 1015 1016 info.scheduleEventAtOffset(100, e1); 1017 info.scheduleEventAtOffset(100, e2); 1018 1019 shadowMediaPlayer.setState(INITIALIZED); 1020 shadowMediaPlayer.doStart(); 1021 scheduler.advanceBy(100); 1022 // Verify that the first event ran 1023 assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse(); 1024 Mockito.verify(e2).run(mediaPlayer, shadowMediaPlayer); 1025 } 1026 1027 @Test 1028 public void testResetCancelsCallbacks() { 1029 shadowMediaPlayer.setState(STARTED); 1030 mediaPlayer.seekTo(100); 1031 MediaEvent e = Mockito.mock(MediaEvent.class); 1032 shadowMediaPlayer.postEventDelayed(e, 200); 1033 mediaPlayer.reset(); 1034 1035 assertThat(scheduler.size()).isEqualTo(0); 1036 } 1037 1038 @Test 1039 public void testReleaseCancelsSeekCallback() { 1040 shadowMediaPlayer.setState(STARTED); 1041 mediaPlayer.seekTo(100); 1042 MediaEvent e = Mockito.mock(MediaEvent.class); 1043 shadowMediaPlayer.postEventDelayed(e, 200); 1044 mediaPlayer.release(); 1045 1046 assertThat(scheduler.size()).isEqualTo(0); 1047 } 1048 1049 @Test 1050 public void testSeekManualCallback() { 1051 // Need to put the player into a state where seeking is allowed 1052 shadowMediaPlayer.setState(STARTED); 1053 // seekDelay of -1 signifies that OnSeekComplete won't be 1054 // invoked automatically by the shadow player itself. 1055 shadowMediaPlayer.setSeekDelay(-1); 1056 1057 assertThat(shadowMediaPlayer.getPendingSeek()).named("pendingSeek before") 1058 .isEqualTo(-1); 1059 int[] positions = { 0, 5, 2, 999 }; 1060 int prevPos = 0; 1061 for (int position : positions) { 1062 mediaPlayer.seekTo(position); 1063 1064 assertThat(shadowMediaPlayer.getPendingSeek()).named("pendingSeek") 1065 .isEqualTo(position); 1066 assertThat(mediaPlayer.getCurrentPosition()).named("pendingSeekCurrentPos") 1067 .isEqualTo(prevPos); 1068 1069 shadowMediaPlayer.invokeSeekCompleteListener(); 1070 1071 assertThat(shadowMediaPlayer.getPendingSeek()).isEqualTo(-1); 1072 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(position); 1073 prevPos = position; 1074 } 1075 } 1076 1077 @Test 1078 public void testPreparedListenerCalled() { 1079 shadowMediaPlayer.invokePreparedListener(); 1080 assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED); 1081 Mockito.verify(preparedListener).onPrepared(mediaPlayer); 1082 } 1083 1084 @Test 1085 public void testPreparedWithoutListenerDoesNotThrowException() { 1086 mediaPlayer.setOnPreparedListener(null); 1087 shadowMediaPlayer.invokePreparedListener(); 1088 1089 assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED); 1090 Mockito.verifyZeroInteractions(preparedListener); 1091 } 1092 1093 @Test 1094 public void testInfoListenerCalled() { 1095 shadowMediaPlayer.invokeInfoListener(21, 32); 1096 Mockito.verify(infoListener).onInfo(mediaPlayer, 21, 32); 1097 } 1098 1099 @Test 1100 public void testInfoWithoutListenerDoesNotThrowException() { 1101 mediaPlayer.setOnInfoListener(null); 1102 shadowMediaPlayer.invokeInfoListener(3, 44); 1103 1104 Mockito.verifyZeroInteractions(infoListener); 1105 } 1106 1107 @Test 1108 public void testErrorListenerCalledNoOnCompleteCalledWhenReturnTrue() { 1109 Mockito.when(errorListener.onError(mediaPlayer, 112, 221)).thenReturn(true); 1110 1111 shadowMediaPlayer.invokeErrorListener(112, 221); 1112 1113 assertThat(shadowMediaPlayer.getState()).isEqualTo(ERROR); 1114 Mockito.verify(errorListener).onError(mediaPlayer, 112, 221); 1115 Mockito.verifyZeroInteractions(completionListener); 1116 } 1117 1118 @Test 1119 public void testErrorListenerCalledOnCompleteCalledWhenReturnFalse() { 1120 Mockito.when(errorListener.onError(mediaPlayer, 0, 0)).thenReturn(false); 1121 1122 shadowMediaPlayer.invokeErrorListener(321, 11); 1123 1124 Mockito.verify(errorListener).onError(mediaPlayer, 321, 11); 1125 Mockito.verify(completionListener).onCompletion(mediaPlayer); 1126 } 1127 1128 @Test 1129 public void testErrorCausesOnCompleteCalledWhenNoErrorListener() { 1130 mediaPlayer.setOnErrorListener(null); 1131 1132 shadowMediaPlayer.invokeErrorListener(321, 21); 1133 1134 Mockito.verifyZeroInteractions(errorListener); 1135 Mockito.verify(completionListener).onCompletion(mediaPlayer); 1136 } 1137 1138 @Test 1139 public void testReleaseStopsScheduler() { 1140 shadowMediaPlayer.doStart(); 1141 mediaPlayer.release(); 1142 assertThat(scheduler.size()).isEqualTo(0); 1143 } 1144 1145 @Test 1146 public void testResetStopsScheduler() { 1147 shadowMediaPlayer.doStart(); 1148 mediaPlayer.reset(); 1149 assertThat(scheduler.size()).isEqualTo(0); 1150 } 1151 1152 @Test 1153 public void testDoStartStop() { 1154 assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse(); 1155 scheduler.advanceBy(100); 1156 shadowMediaPlayer.doStart(); 1157 assertThat(shadowMediaPlayer.isReallyPlaying()).isTrue(); 1158 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(0); 1159 assertThat(shadowMediaPlayer.getState()).isSameAs(IDLE); 1160 1161 scheduler.advanceBy(100); 1162 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100); 1163 1164 shadowMediaPlayer.doStop(); 1165 assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse(); 1166 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100); 1167 assertThat(shadowMediaPlayer.getState()).isSameAs(IDLE); 1168 1169 scheduler.advanceBy(50); 1170 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100); 1171 } 1172 1173 @Test 1174 public void testScheduleErrorAtOffsetWhileNotPlaying() { 1175 info.scheduleErrorAtOffset(500, 1, 3); 1176 shadowMediaPlayer.setState(INITIALIZED); 1177 shadowMediaPlayer.setState(PREPARED); 1178 mediaPlayer.start(); 1179 1180 scheduler.advanceBy(499); 1181 Mockito.verifyZeroInteractions(errorListener); 1182 1183 scheduler.advanceBy(1); 1184 Mockito.verify(errorListener).onError(mediaPlayer, 1, 3); 1185 assertThat(shadowMediaPlayer.getState()).isSameAs(ERROR); 1186 assertThat(scheduler.advanceToLastPostedRunnable()).isFalse(); 1187 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(500); 1188 } 1189 1190 @Test 1191 public void testScheduleErrorAtOffsetInPast() { 1192 info.scheduleErrorAtOffset(200, 1, 2); 1193 shadowMediaPlayer.setState(INITIALIZED); 1194 shadowMediaPlayer.setCurrentPosition(400); 1195 shadowMediaPlayer.setState(PAUSED); 1196 mediaPlayer.start(); 1197 scheduler.unPause(); 1198 Mockito.verifyZeroInteractions(errorListener); 1199 } 1200 1201 @Test 1202 public void testScheduleBufferUnderrunAtOffset() { 1203 info.scheduleBufferUnderrunAtOffset(100, 50); 1204 shadowMediaPlayer.setState(INITIALIZED); 1205 shadowMediaPlayer.setState(PREPARED); 1206 mediaPlayer.start(); 1207 1208 scheduler.advanceBy(99); 1209 1210 Mockito.verifyZeroInteractions(infoListener); 1211 1212 scheduler.advanceBy(1); 1213 Mockito.verify(infoListener).onInfo(mediaPlayer, 1214 MediaPlayer.MEDIA_INFO_BUFFERING_START, 0); 1215 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100); 1216 assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse(); 1217 1218 scheduler.advanceBy(49); 1219 Mockito.verifyZeroInteractions(infoListener); 1220 1221 scheduler.advanceBy(1); 1222 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100); 1223 Mockito.verify(infoListener).onInfo(mediaPlayer, 1224 MediaPlayer.MEDIA_INFO_BUFFERING_END, 0); 1225 1226 scheduler.advanceBy(100); 1227 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(200); 1228 } 1229 1230 @Test 1231 public void testRemoveEventAtOffset() { 1232 shadowMediaPlayer.setState(PREPARED); 1233 mediaPlayer.start(); 1234 1235 scheduler.advanceBy(200); 1236 1237 MediaEvent e = info.scheduleInfoAtOffset( 1238 500, 1, 3); 1239 1240 scheduler.advanceBy(299); 1241 info.removeEventAtOffset(500, e); 1242 scheduler.advanceToLastPostedRunnable(); 1243 Mockito.verifyZeroInteractions(infoListener); 1244 } 1245 1246 @Test 1247 public void testRemoveEvent() { 1248 shadowMediaPlayer.setState(PREPARED); 1249 mediaPlayer.start(); 1250 1251 scheduler.advanceBy(200); 1252 1253 MediaEvent e = info.scheduleInfoAtOffset(500, 1, 3); 1254 1255 scheduler.advanceBy(299); 1256 shadowMediaPlayer.doStop(); 1257 info.removeEvent(e); 1258 shadowMediaPlayer.doStart(); 1259 scheduler.advanceToLastPostedRunnable(); 1260 Mockito.verifyZeroInteractions(infoListener); 1261 } 1262 1263 @Test 1264 public void testScheduleMultipleRunnables() { 1265 shadowMediaPlayer.setState(PREPARED); 1266 scheduler.advanceBy(25); 1267 mediaPlayer.start(); 1268 1269 scheduler.advanceBy(200); 1270 assertThat(scheduler.size()).isEqualTo(1); 1271 shadowMediaPlayer.doStop(); 1272 info.scheduleInfoAtOffset(250, 2, 4); 1273 shadowMediaPlayer.doStart(); 1274 assertThat(scheduler.size()).isEqualTo(1); 1275 1276 MediaEvent e1 = Mockito.mock(MediaEvent.class); 1277 1278 shadowMediaPlayer.doStop(); 1279 info.scheduleEventAtOffset(400, e1); 1280 shadowMediaPlayer.doStart(); 1281 1282 scheduler.advanceBy(49); 1283 Mockito.verifyZeroInteractions(infoListener); 1284 scheduler.advanceBy(1); 1285 Mockito.verify(infoListener).onInfo(mediaPlayer, 2, 4); 1286 scheduler.advanceBy(149); 1287 shadowMediaPlayer.doStop(); 1288 info.scheduleErrorAtOffset(675, 32, 22); 1289 shadowMediaPlayer.doStart(); 1290 Mockito.verifyZeroInteractions(e1); 1291 scheduler.advanceBy(1); 1292 Mockito.verify(e1).run(mediaPlayer, shadowMediaPlayer); 1293 1294 mediaPlayer.pause(); 1295 assertThat(scheduler.size()).isEqualTo(0); 1296 scheduler.advanceBy(324); 1297 MediaEvent e2 = Mockito.mock(MediaEvent.class); 1298 info.scheduleEventAtOffset(680, e2); 1299 mediaPlayer.start(); 1300 scheduler.advanceBy(274); 1301 Mockito.verifyZeroInteractions(errorListener); 1302 1303 scheduler.advanceBy(1); 1304 Mockito.verify(errorListener).onError(mediaPlayer, 32, 22); 1305 assertThat(scheduler.size()).isEqualTo(0); 1306 assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(675); 1307 assertThat(shadowMediaPlayer.getState()).isSameAs(ERROR); 1308 Mockito.verifyZeroInteractions(e2); 1309 } 1310 1311 @Test 1312 public void testSetDataSourceExceptionWithWrongExceptionTypeAsserts() { 1313 boolean fail = false; 1314 Map<DataSource,Exception> exceptions = ReflectionHelpers.getStaticField(ShadowMediaPlayer.class, "exceptions"); 1315 DataSource ds = toDataSource("dummy"); 1316 Exception e = new CloneNotSupportedException(); // just a convenient, non-RuntimeException in java.lang 1317 exceptions.put(ds, e); 1318 1319 try { 1320 shadowMediaPlayer.setDataSource(ds); 1321 fail = true; 1322 } catch (AssertionError a) { 1323 } catch (IOException ioe) { 1324 fail("Got exception <" + ioe + ">; expecting assertion"); 1325 } 1326 if (fail) { 1327 fail("setDataSource() should assert with non-IOException,non-RuntimeException"); 1328 } 1329 } 1330 1331 @Test 1332 public void testSetDataSourceCustomExceptionOverridesIllegalState() { 1333 shadowMediaPlayer.setState(PREPARED); 1334 ShadowMediaPlayer.addException(toDataSource("dummy"), new IOException()); 1335 try { 1336 mediaPlayer.setDataSource("dummy"); 1337 fail("Expecting IOException to be thrown"); 1338 } catch (IOException eThrown) { 1339 } catch (Exception eThrown) { 1340 fail(eThrown + " was thrown, expecting IOException"); 1341 } 1342 } 1343 1344 @Test 1345 public void testGetSetLooping() { 1346 assertThat(mediaPlayer.isLooping()).isFalse(); 1347 mediaPlayer.setLooping(true); 1348 assertThat(mediaPlayer.isLooping()).isTrue(); 1349 mediaPlayer.setLooping(false); 1350 assertThat(mediaPlayer.isLooping()).isFalse(); 1351 } 1352 1353 /** 1354 * If the looping mode was being set to {@code true} 1355 * {@link MediaPlayer#setLooping(boolean)}, the MediaPlayer object shall 1356 * remain in the Started state. 1357 */ 1358 @Test 1359 public void testSetLoopingCalledWhilePlaying() { 1360 shadowMediaPlayer.setState(PREPARED); 1361 mediaPlayer.start(); 1362 scheduler.advanceBy(200); 1363 1364 mediaPlayer.setLooping(true); 1365 scheduler.advanceBy(1100); 1366 1367 Mockito.verifyZeroInteractions(completionListener); 1368 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300); 1369 1370 mediaPlayer.setLooping(false); 1371 scheduler.advanceBy(699); 1372 Mockito.verifyZeroInteractions(completionListener); 1373 1374 scheduler.advanceBy(1); 1375 Mockito.verify(completionListener).onCompletion(mediaPlayer); 1376 } 1377 1378 @Test 1379 public void testSetLoopingCalledWhileStartable() { 1380 final State[] startableStates = { PREPARED, PAUSED }; 1381 for (State state : startableStates) { 1382 shadowMediaPlayer.setCurrentPosition(500); 1383 shadowMediaPlayer.setState(state); 1384 1385 mediaPlayer.setLooping(true); 1386 mediaPlayer.start(); 1387 1388 scheduler.advanceBy(700); 1389 Mockito.verifyZeroInteractions(completionListener); 1390 assertThat(mediaPlayer.getCurrentPosition()).named(state.toString()) 1391 .isEqualTo(200); 1392 } 1393 } 1394 1395 /** 1396 * While in the PlaybackCompleted state, calling start() can restart the 1397 * playback from the beginning of the audio/video source. 1398 */ 1399 @Test 1400 public void testStartAfterPlaybackCompleted() { 1401 shadowMediaPlayer.setState(PLAYBACK_COMPLETED); 1402 shadowMediaPlayer.setCurrentPosition(1000); 1403 1404 mediaPlayer.start(); 1405 assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0); 1406 } 1407 1408 @Test 1409 public void testResetStaticState() { 1410 ShadowMediaPlayer.CreateListener createListener = Mockito 1411 .mock(ShadowMediaPlayer.CreateListener.class); 1412 ShadowMediaPlayer.setCreateListener(createListener); 1413 assertThat(ShadowMediaPlayer.createListener) 1414 .named("createListener") 1415 .isSameAs(createListener); 1416 DataSource dummy = toDataSource("stuff"); 1417 IOException e = new IOException(); 1418 addException(dummy, e); 1419 1420 try { 1421 shadowMediaPlayer.setState(IDLE); 1422 shadowMediaPlayer.setDataSource(dummy); 1423 fail("Expected exception thrown"); 1424 } catch (IOException e2) { 1425 assertThat(e2).named("thrown exception").isSameAs(e); 1426 } 1427 // Check that the mediaInfo was cleared 1428 shadowMediaPlayer.doSetDataSource(defaultSource); 1429 assertThat(shadowMediaPlayer.getMediaInfo()).named("mediaInfo:before").isNotNull(); 1430 1431 ShadowMediaPlayer.resetStaticState(); 1432 1433 // Check that the listener was cleared. 1434 assertThat(ShadowMediaPlayer.createListener) 1435 .named("createListener") 1436 .isNull(); 1437 1438 // Check that the mediaInfo was cleared. 1439 try { 1440 shadowMediaPlayer.doSetDataSource(defaultSource); 1441 fail("Expected exception thrown"); 1442 } catch (IllegalArgumentException ie) { 1443 // We expect this if the static state has been cleared. 1444 } 1445 1446 // Check that the exception was cleared. 1447 try { 1448 shadowMediaPlayer.setState(IDLE); 1449 ShadowMediaPlayer.addMediaInfo(dummy, info); 1450 shadowMediaPlayer.setDataSource(dummy); 1451 } catch (IOException e2) { 1452 fail("Exception was not cleared by resetStaticState() for <" + dummy + ">" + e2); 1453 } 1454 } 1455 1456 @Test 1457 public void setDataSourceException_withRuntimeException() { 1458 RuntimeException e = new RuntimeException("some dummy message"); 1459 addException(toDataSource("dummy"), e); 1460 try { 1461 mediaPlayer.setDataSource("dummy"); 1462 fail("Expected exception thrown"); 1463 } catch (Exception caught) { 1464 assertThat(caught).isSameAs(e); 1465 assertThat(e.getStackTrace()[0].getClassName()) 1466 .named("Stack trace should originate in Shadow") 1467 .isEqualTo(ShadowMediaPlayer.class.getName()); 1468 } 1469 } 1470 1471 @Test 1472 public void setDataSourceException_withIOException() { 1473 IOException e = new IOException("some dummy message"); 1474 addException(toDataSource("dummy"), e); 1475 shadowMediaPlayer.setState(IDLE); 1476 try { 1477 mediaPlayer.setDataSource("dummy"); 1478 fail("Expected exception thrown"); 1479 } catch (Exception caught) { 1480 assertThat(caught).isSameAs(e); 1481 assertThat(e.getStackTrace()[0].getClassName()) 1482 .named("Stack trace should originate in Shadow") 1483 .isEqualTo(ShadowMediaPlayer.class.getName()); 1484 assertThat(shadowMediaPlayer.getState()).named( 1485 "State after " + e + " thrown should be unchanged").isSameAs(IDLE); 1486 } 1487 } 1488 1489 @Test 1490 public void setDataSource_forNoDataSource_asserts() { 1491 try { 1492 mediaPlayer.setDataSource("some unspecified data source"); 1493 fail("Expected exception thrown"); 1494 } catch (IllegalArgumentException a) { 1495 assertThat(a.getMessage()).named("assertionMessage") 1496 .contains("addException"); 1497 assertThat(a.getMessage()).named("assertionMessage") 1498 .contains("addMediaInfo"); 1499 } catch (Exception e) { 1500 throw new RuntimeException("Unexpected exception", e); 1501 } 1502 } 1503 1504 @Test 1505 public void instantiateOnBackgroundThread() throws ExecutionException, InterruptedException { 1506 ShadowMediaPlayer shadowMediaPlayer = 1507 Executors.newSingleThreadExecutor() 1508 .submit( 1509 () -> { 1510 // This thread does not have a prepared looper, so the main looper is used 1511 MediaPlayer mediaPlayer = Shadow.newInstanceOf(MediaPlayer.class); 1512 return shadowOf(mediaPlayer); 1513 }) 1514 .get(); 1515 AtomicBoolean ran = new AtomicBoolean(false); 1516 shadowMediaPlayer.postEvent( 1517 new MediaEvent() { 1518 @Override 1519 public void run(MediaPlayer mp, ShadowMediaPlayer smp) { 1520 assertThat(Looper.myLooper()).isSameAs(Looper.getMainLooper()); 1521 ran.set(true); 1522 } 1523 }); 1524 scheduler.advanceToLastPostedRunnable(); 1525 assertThat(ran.get()).isTrue(); 1526 } 1527 } 1528