Niedzielski has uploaded a new change for review. https://gerrit.wikimedia.org/r/250903
Change subject: Add pronounciation media player logic ...................................................................... Add pronounciation media player logic Add logic for playing media. The initial network request will come in a later patch. Bug: T114524 Change-Id: I4cf7b0be940085aad1a6931d5eee3215de80488c --- M app/src/main/java/org/wikipedia/media/AvPlayer.java A app/src/main/java/org/wikipedia/media/AvPlayerImplementation.java M app/src/main/java/org/wikipedia/media/DefaultAvPlayer.java A app/src/main/java/org/wikipedia/media/MediaPlayerImplementation.java A app/src/main/java/org/wikipedia/media/State.java M app/src/main/java/org/wikipedia/richtext/AudioUrlSpan.java M app/src/main/java/org/wikipedia/richtext/TextViewSpanOnTouchListener.java M app/src/main/java/org/wikipedia/views/ArticleHeaderView.java A app/src/test/java/org/wikipedia/media/DefaultAvPlayerTest.java A app/src/test/java/org/wikipedia/media/FakeAvPlayerImplementation.java A app/src/test/java/org/wikipedia/media/StateTest.java 11 files changed, 1,065 insertions(+), 37 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/wikipedia refs/changes/03/250903/1 diff --git a/app/src/main/java/org/wikipedia/media/AvPlayer.java b/app/src/main/java/org/wikipedia/media/AvPlayer.java index 01107d1..0acab89 100644 --- a/app/src/main/java/org/wikipedia/media/AvPlayer.java +++ b/app/src/main/java/org/wikipedia/media/AvPlayer.java @@ -1,11 +1,29 @@ package org.wikipedia.media; +import android.support.annotation.NonNull; + public interface AvPlayer { - void init(); + interface Callback { + void onSuccess(); + } + + interface ErrorCallback { + void onError(); + } + void deinit(); - void load(String path); - void play(); - void togglePlayback(); + void init(); + + void load(@NonNull String path, + @NonNull Callback callback, + @NonNull ErrorCallback errorCallback); + void stop(); - void seek(int millis); + + void play(@NonNull Callback callback, @NonNull ErrorCallback errorCallback); + void play(@NonNull String path, + @NonNull Callback callback, + @NonNull ErrorCallback errorCallback); + + void pause(); } \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/media/AvPlayerImplementation.java b/app/src/main/java/org/wikipedia/media/AvPlayerImplementation.java new file mode 100644 index 0000000..8348d7d --- /dev/null +++ b/app/src/main/java/org/wikipedia/media/AvPlayerImplementation.java @@ -0,0 +1,14 @@ +package org.wikipedia.media; + +import android.support.annotation.NonNull; + +interface AvPlayerImplementation { + void deinit(); + void init(); + void load(@NonNull String path, + @NonNull AvPlayer.Callback callback, + @NonNull AvPlayer.ErrorCallback errorCallback); + void stop(); + void play(@NonNull AvPlayer.Callback callback, @NonNull AvPlayer.ErrorCallback errorCallback); + void pause(); +} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/media/DefaultAvPlayer.java b/app/src/main/java/org/wikipedia/media/DefaultAvPlayer.java index 5514a53..770d78d 100644 --- a/app/src/main/java/org/wikipedia/media/DefaultAvPlayer.java +++ b/app/src/main/java/org/wikipedia/media/DefaultAvPlayer.java @@ -1,36 +1,119 @@ package org.wikipedia.media; +import android.support.annotation.NonNull; + public class DefaultAvPlayer implements AvPlayer { - @Override - public void init() { + @NonNull + private final AvPlayerImplementation player; + @NonNull + private final State state = new State(); + + public DefaultAvPlayer(@NonNull AvPlayerImplementation player) { + this.player = player; } @Override public void deinit() { + if (state.isInit()) { + player.deinit(); + state.setDeinit(); + } } @Override - public void load(String path) { - + public void init() { + if (state.isDeinit()) { + player.init(); + state.setInit(); + } } @Override - public void play() { - - } - - @Override - public void togglePlayback() { - + public void load(@NonNull String path, + @NonNull final Callback callback, + @NonNull final ErrorCallback errorCallback) { + init(); + if (!state.isLoading(path) && !state.isLoaded(path)) { + state.setLoading(path); + player.load(path, new Callback() { + @Override + public void onSuccess() { + state.setLoaded(); + if (state.isPlaying()) { + player.play(new StopCallbackWrapper(callback), new StopErrorCallbackWrapper(errorCallback)); + } else { + callback.onSuccess(); + } + } + }, new ErrorCallback() { + @Override + public void onError() { + state.setInit(); + errorCallback.onError(); + } + }); + } } @Override public void stop() { - + if (state.isLoaded() && !state.isStopped()) { + player.stop(); + } + state.setStopped(); } @Override - public void seek(int millis) { - + public void play(@NonNull Callback callback, @NonNull ErrorCallback errorCallback) { + if (state.isLoaded() && !state.isPlaying()) { + state.setPlaying(); + player.play(new StopCallbackWrapper(callback), new StopErrorCallbackWrapper(errorCallback)); + } else { + state.setPlaying(); + } } -} + + @Override + public void play(@NonNull String path, + @NonNull Callback callback, + @NonNull ErrorCallback errorCallback) { + load(path, callback, errorCallback); + play(callback, errorCallback); + } + + @Override + public void pause() { + if (state.isLoaded() && state.isPlaying()) { + player.pause(); + } + state.setPaused(); + } + + private class StopCallbackWrapper implements Callback { + @NonNull private final Callback callback; + + StopCallbackWrapper(@NonNull Callback callback) { + this.callback = callback; + } + + @Override + public void onSuccess() { + state.setStopped(); + callback.onSuccess(); + } + } + + private class StopErrorCallbackWrapper implements ErrorCallback { + @NonNull private final ErrorCallback errorCallback; + + StopErrorCallbackWrapper(@NonNull ErrorCallback errorCallback) { + this.errorCallback = errorCallback; + } + + @Override + public void onError() { + state.setStopped(); + errorCallback.onError(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/media/MediaPlayerImplementation.java b/app/src/main/java/org/wikipedia/media/MediaPlayerImplementation.java new file mode 100644 index 0000000..670e906 --- /dev/null +++ b/app/src/main/java/org/wikipedia/media/MediaPlayerImplementation.java @@ -0,0 +1,177 @@ +package org.wikipedia.media; + +import android.media.MediaPlayer; +import android.support.annotation.NonNull; + +import org.wikipedia.util.log.L; + +import java.io.IOException; + +public class MediaPlayerImplementation implements AvPlayerImplementation { + private static final boolean VERBOSE = false; + private boolean paused; + + @NonNull private final MediaPlayer player = new MediaPlayer(); + + @Override + public void deinit() { + if (VERBOSE) { + L.v("Releasing"); + } + player.release(); + } + + @Override + public void init() { + if (VERBOSE) { + L.v("Resetting"); + } + player.reset(); + } + + @Override + public void load(@NonNull String path, + @NonNull AvPlayer.Callback callback, + @NonNull AvPlayer.ErrorCallback errorCallback) { + load(path, new PreparedListenerCallbackWrapper(callback), + new ErrorListenerErrorCallbackWrapper(errorCallback)); + } + + @Override + public void stop() { + if (VERBOSE) { + L.v("Stopping"); + } + + // Do not call MediaPlayer.stop(). This requires going through the whole lifecycle again. + if (!paused) { + pause(); + } + player.seekTo(0); + } + + @Override + public void play(@NonNull AvPlayer.Callback callback, + @NonNull AvPlayer.ErrorCallback errorCallback) { + play(new CompletionListenerCallbackWrapper(callback), + new ErrorListenerErrorCallbackWrapper(errorCallback)); + } + + @Override + public void pause() { + if (VERBOSE) { + L.v("Pausing"); + } + paused = true; + player.pause(); + } + + private void load(@NonNull String path, + @NonNull MediaPlayer.OnPreparedListener listener, + @NonNull MediaPlayer.OnErrorListener errorListener) { + if (VERBOSE) { + L.v("Loading"); + } + paused = false; + player.setOnPreparedListener(listener); + player.setOnErrorListener(errorListener); + if (setDataSource(path)) { + player.prepareAsync(); + } else { + errorListener.onError(player, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + } + } + + private void play(@NonNull MediaPlayer.OnCompletionListener listener, + @NonNull MediaPlayer.OnErrorListener errorListener) { + if (VERBOSE) { + L.v("Playing"); + } + paused = false; + player.setOnCompletionListener(listener); + player.setOnErrorListener(errorListener); + player.start(); + } + + private boolean setDataSource(@NonNull String path) { + try { + player.setDataSource(path); + return true; + } catch (IOException e) { + L.d(e); + return false; + } + } + + private abstract static class CallbackWrapper { + @NonNull private final AvPlayer.Callback callback; + CallbackWrapper(@NonNull AvPlayer.Callback callback) { + this.callback = callback; + } + + protected void onSuccess() { + callback.onSuccess(); + } + } + + private abstract static class ErrorCallbackWrapper { + @NonNull private final AvPlayer.ErrorCallback errorCallback; + ErrorCallbackWrapper(@NonNull AvPlayer.ErrorCallback errorCallback) { + this.errorCallback = errorCallback; + } + + protected void onError() { + errorCallback.onError(); + } + } + + private static class PreparedListenerCallbackWrapper extends CallbackWrapper implements + MediaPlayer.OnPreparedListener { + PreparedListenerCallbackWrapper(@NonNull AvPlayer.Callback callback) { + super(callback); + } + + @Override + public void onPrepared(MediaPlayer player) { + if (VERBOSE) { + L.v("Loaded"); + } + onSuccess(); + } + } + + private static class CompletionListenerCallbackWrapper extends CallbackWrapper implements + MediaPlayer.OnCompletionListener { + CompletionListenerCallbackWrapper(@NonNull AvPlayer.Callback callback) { + super(callback); + } + + @Override + public void onCompletion(MediaPlayer mp) { + if (VERBOSE) { + L.v("Stopped"); + } + onSuccess(); + } + } + + private static class ErrorListenerErrorCallbackWrapper extends ErrorCallbackWrapper implements + MediaPlayer.OnErrorListener { + ErrorListenerErrorCallbackWrapper(@NonNull AvPlayer.ErrorCallback errorCallback) { + super(errorCallback); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + if (VERBOSE) { + L.v("Error: what=" + formatHex(what) + " extra=" + formatHex(extra)); + } + onError(); + return true; + } + + private String formatHex(int hex) { + return String.format("x%08x", hex); + } + } +} diff --git a/app/src/main/java/org/wikipedia/media/State.java b/app/src/main/java/org/wikipedia/media/State.java new file mode 100644 index 0000000..4770aa5 --- /dev/null +++ b/app/src/main/java/org/wikipedia/media/State.java @@ -0,0 +1,100 @@ +package org.wikipedia.media; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.wikipedia.util.StringUtil; + +class State { + private enum LoadState { + DEINIT, INIT, LOADING, LOADED + } + + private enum PlayState { + STOPPED, PLAYING, PAUSED + } + + @NonNull + private LoadState loadState = LoadState.DEINIT; + @NonNull + private PlayState playState = PlayState.STOPPED; + @Nullable + private String path; + + @Nullable + public String getPath() { + return path; + } + + public boolean isDeinit() { + return loadState == LoadState.DEINIT; + } + + public void setDeinit() { + loadState = LoadState.DEINIT; + playState = PlayState.STOPPED; + } + + public boolean isInit() { + return !isDeinit(); + } + + public void setInit() { + loadState = LoadState.INIT; + } + + public boolean isLoading() { + return loadState == LoadState.LOADING; + } + + public boolean isLoading(@Nullable String path) { + return isLoading() && isPathIdentical(path); + } + + public void setLoading(@Nullable String path) { + if (!isLoaded(path)) { + loadState = LoadState.LOADING; + this.path = path; + } + } + + public boolean isLoaded() { + return loadState == LoadState.LOADED; + } + + public boolean isLoaded(@Nullable String path) { + return isLoaded() && isPathIdentical(path); + } + + public void setLoaded() { + loadState = LoadState.LOADED; + } + + public boolean isStopped() { + return playState == PlayState.STOPPED; + } + + public void setStopped() { + playState = PlayState.STOPPED; + } + + public boolean isPlaying() { + return playState == PlayState.PLAYING; + } + + public void setPlaying() { + playState = PlayState.PLAYING; + } + + public boolean isPaused() { + return playState == PlayState.PAUSED; + } + + public void setPaused() { + playState = PlayState.PAUSED; + } + + private boolean isPathIdentical(@Nullable String path) { + return StringUtil.equals(this.path, path); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/richtext/AudioUrlSpan.java b/app/src/main/java/org/wikipedia/richtext/AudioUrlSpan.java index 90fb13b..21a84a9 100644 --- a/app/src/main/java/org/wikipedia/richtext/AudioUrlSpan.java +++ b/app/src/main/java/org/wikipedia/richtext/AudioUrlSpan.java @@ -24,10 +24,16 @@ @NonNull private final AvPlayer player; - public AudioUrlSpan(@NonNull View view, @NonNull AvPlayer player) { + @NonNull + private final AvCallback avCallback = new AvCallback(); + + @NonNull + private final String path; + + public AudioUrlSpan(@NonNull View view, @NonNull AvPlayer player, @NonNull String path) { super(view, drawable(view.getContext())); this.player = player; - view.addOnAttachStateChangeListener(new ViewAttachListener()); + this.path = path; } public void setTint(@ColorInt int color) { @@ -43,7 +49,7 @@ public void start() { showIcon(PLAY_ICON_LEVEL); super.start(); - player.play(); + player.play(path, avCallback, avCallback); } @Override @@ -51,12 +57,6 @@ showIcon(STOP_ICON_LEVEL); super.stop(); player.stop(); - } - - @Override - public void toggle() { - super.toggle(); - player.togglePlayback(); } @Override @@ -104,15 +104,16 @@ return context.getResources().getDimensionPixelSize(id); } - private class ViewAttachListener implements View.OnAttachStateChangeListener { + private class AvCallback implements AvPlayer.Callback, AvPlayer.ErrorCallback { @Override - public void onViewAttachedToWindow(View view) { - player.init(); + public void onSuccess() { + stop(); } + @Override - public void onViewDetachedFromWindow(View v) { - player.deinit(); + public void onError() { + stop(); } } } \ No newline at end of file diff --git a/app/src/main/java/org/wikipedia/richtext/TextViewSpanOnTouchListener.java b/app/src/main/java/org/wikipedia/richtext/TextViewSpanOnTouchListener.java index 01f9edc..e40604f 100644 --- a/app/src/main/java/org/wikipedia/richtext/TextViewSpanOnTouchListener.java +++ b/app/src/main/java/org/wikipedia/richtext/TextViewSpanOnTouchListener.java @@ -10,7 +10,7 @@ import android.widget.TextView; public class TextViewSpanOnTouchListener implements View.OnTouchListener { - @Nullable private TextView textView; + @NonNull private TextView textView; public TextViewSpanOnTouchListener(@NonNull TextView textView) { this.textView = textView; diff --git a/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java b/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java index 031836a..69168cd 100644 --- a/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java +++ b/app/src/main/java/org/wikipedia/views/ArticleHeaderView.java @@ -32,7 +32,9 @@ import org.wikipedia.R; import org.wikipedia.Utils; import org.wikipedia.WikipediaApp; +import org.wikipedia.media.AvPlayer; import org.wikipedia.media.DefaultAvPlayer; +import org.wikipedia.media.MediaPlayerImplementation; import org.wikipedia.page.leadimages.ImageViewWithFace; import org.wikipedia.page.leadimages.ImageViewWithFace.OnImageLoadListener; import org.wikipedia.richtext.LeadingSpan; @@ -54,6 +56,8 @@ @NonNull private CharSequence title = ""; @NonNull private CharSequence subtitle = ""; @Nullable private String pronunciationUrl; + + @NonNull private final AvPlayer avPlayer = new DefaultAvPlayer(new MediaPlayerImplementation()); public ArticleHeaderView(Context context) { super(context); @@ -193,7 +197,21 @@ return pronunciationUrl != null; } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + avPlayer.init(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + avPlayer.deinit(); + } + private void updateText() { + avPlayer.stop(); + SpannableStringBuilder builder = new SpannableStringBuilder(title); if (hasPronunciation()) { @@ -210,7 +228,7 @@ } private Spanned pronunciationSpanned() { - AudioUrlSpan span = new AudioUrlSpan(text, new DefaultAvPlayer()); + AudioUrlSpan span = new AudioUrlSpan(text, avPlayer, pronunciationUrl); span.setTint(hasImage() ? Color.WHITE : getContrastingThemeColor()); return RichTextUtil.setSpans(new SpannableString(" "), 0, @@ -267,7 +285,7 @@ private void initText() { // TODO: replace with android:fontFamily="serif" attribute when our minimum API level is - // Jelly Bean, API 16, or we make custom typeface attribute. + // Jelly Bean, API 16, or if we make custom typeface attribute. text.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL)); } diff --git a/app/src/test/java/org/wikipedia/media/DefaultAvPlayerTest.java b/app/src/test/java/org/wikipedia/media/DefaultAvPlayerTest.java new file mode 100644 index 0000000..f3ae699 --- /dev/null +++ b/app/src/test/java/org/wikipedia/media/DefaultAvPlayerTest.java @@ -0,0 +1,226 @@ +package org.wikipedia.media; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wikipedia.test.TestRunner; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@RunWith(TestRunner.class) +public class DefaultAvPlayerTest { + private static final String PATH_A = "http://pathA"; + private static final String PATH_B = "http://pathB"; + + private final FakeAvPlayerImplementation implementation = spy(new FakeAvPlayerImplementation()); + private AvPlayer subject = new DefaultAvPlayer(implementation); + private final AvPlayer.Callback callback = mock(AvPlayer.Callback.class); + private final AvPlayer.ErrorCallback errorCallback = mock(AvPlayer.ErrorCallback.class); + + @Test + public void testDeinitDeinit() { + subject.deinit(); + verify(implementation, never()).deinit(); + } + + @Test + public void testDeinitInit() { + subject.init(); + subject.deinit(); + verify(implementation).deinit(); + } + + @Test + public void testDeinitLoad() { + implementation.setAsyncLoadFailure(true); + load(PATH_A); + subject.deinit(); + verify(implementation).deinit(); + } + + @Test + public void testInitConstructor() { + verify(implementation, never()).init(); + } + + @Test + public void testInitDeinit() { + subject.deinit(); + subject.init(); + verify(implementation).init(); + } + + @Test + public void testInitInit() { + subject.init(); + subject.init(); + verify(implementation).init(); + } + + @Test + public void testInitLoad() { + load(PATH_A); + verify(implementation).init(); + } + + @Test + public void testInitStop() { + subject.stop(); + verify(implementation, never()).init(); + } + + @Test + public void testLoadLoad() { + load(PATH_A); + verifyLoad(1, PATH_A); + verifyCallback(1); + verifyErrorCallback(0); + } + + @Test + public void testLoadReloadSync() { + load(PATH_A); + load(PATH_B); + verifyLoad(1, PATH_B); + verifyCallback(2); + verifyErrorCallback(0); + } + + @Test + public void testLoadReloadAsync() { + implementation.setAsyncLoadFailure(true); + load(PATH_A); + implementation.setAsyncLoadFailure(false); + load(PATH_B); + verifyLoad(1, PATH_B); + verifyCallback(1); + verifyErrorCallback(0); + } + + @Test + public void testLoadFail() { + implementation.setSyncLoadFailure(true); + load(PATH_A); + verifyLoad(1, PATH_A); + verifyCallback(0); + verifyErrorCallback(1); + } + + @Test + public void testLoadPlay() { + play(PATH_A); + verifyLoad(1, PATH_A); + verifyCallback(1); + verifyErrorCallback(0); + } + + @Test + public void testStopConstructor() { + subject.stop(); + verify(implementation, never()).stop(); + } + + @Test + public void testStopLoad() { + implementation.setAsyncLoadFailure(true); + load(PATH_A); + subject.stop(); + verify(implementation, never()).stop(); + verifyCallback(0); + verifyErrorCallback(0); + } + + @Test + public void testStopPlaySync() { + play(PATH_A); + subject.stop(); + verify(implementation, never()).stop(); + verifyCallback(1); + verifyErrorCallback(0); + } + + @Test + public void testStopPlayAsync() { + implementation.setAsyncPlayFailure(true); + play(PATH_A); + subject.stop(); + verify(implementation).stop(); + verifyCallback(0); + verifyErrorCallback(0); + } + + @Test + public void testPlayPlaySync() { + play(PATH_A); + play(PATH_A); + verifyPlay(2); + verifyCallback(2); + verifyErrorCallback(0); + } + + @Test + public void testPlayPlaySyncFail() { + implementation.setSyncPlayFailure(true); + play(PATH_A); + verifyPlay(1); + verifyCallback(0); + verifyErrorCallback(1); + } + + @Test + public void testPlayLoadSyncFail() { + implementation.setSyncLoadFailure(true); + play(PATH_A); + verifyPlay(0); + verifyCallback(0); + verifyErrorCallback(1); + } + + @Test + public void testPlayPlayAsync() { + implementation.setAsyncPlayFailure(true); + play(PATH_A); + implementation.setAsyncPlayFailure(false); + play(PATH_A); + verifyPlay(1); + verifyCallback(0); + verifyErrorCallback(0); + } + + private void load(String path) { + subject.load(path, callback, errorCallback); + } + + private void play(String path) { + subject.play(path, callback, errorCallback); + } + + private void verifyCallback(int times) { + verify(callback, times(times)).onSuccess(); + } + + private void verifyErrorCallback(int times) { + verify(errorCallback, times(times)).onError(); + } + + private void verifyLoad(int times, String path) { + verify(implementation, times(times)).load(eq(path), anyCallback(), anyErrorCallback()); + } + + private void verifyPlay(int times) { + verify(implementation, times(times)).play(anyCallback(), anyErrorCallback()); + } + + private AvPlayer.Callback anyCallback() { + return any(AvPlayer.Callback.class); + } + + private AvPlayer.ErrorCallback anyErrorCallback() { + return any(AvPlayer.ErrorCallback.class); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/wikipedia/media/FakeAvPlayerImplementation.java b/app/src/test/java/org/wikipedia/media/FakeAvPlayerImplementation.java new file mode 100644 index 0000000..34e1e59 --- /dev/null +++ b/app/src/test/java/org/wikipedia/media/FakeAvPlayerImplementation.java @@ -0,0 +1,59 @@ +package org.wikipedia.media; + +import android.support.annotation.NonNull; + +class FakeAvPlayerImplementation implements AvPlayerImplementation { + private boolean asyncLoadFailure; + private boolean syncLoadFailure; + private boolean asyncPlayFailure; + private boolean syncPlayFailure; + + public void setAsyncLoadFailure(boolean enabled) { + asyncLoadFailure = enabled; + } + + public void setSyncLoadFailure(boolean enabled) { + syncLoadFailure = enabled; + } + + public void setAsyncPlayFailure(boolean enabled) { + asyncPlayFailure = enabled; + } + + public void setSyncPlayFailure(boolean enabled) { + syncPlayFailure = enabled; + } + + @Override public void deinit() { } + + @Override public void init() { } + + @Override + public void load(@NonNull String path, + @NonNull AvPlayer.Callback callback, + @NonNull AvPlayer.ErrorCallback errorCallback) { + if (asyncLoadFailure) { + // no callback + } else if (syncLoadFailure) { + errorCallback.onError(); + } else { + callback.onSuccess(); + } + } + + @Override public void stop() { } + + @Override + public void play(@NonNull AvPlayer.Callback callback, + @NonNull AvPlayer.ErrorCallback errorCallback) { + if (asyncPlayFailure) { + // no callback + } else if (syncPlayFailure) { + errorCallback.onError(); + } else { + callback.onSuccess(); + } + } + + @Override public void pause() { } +} \ No newline at end of file diff --git a/app/src/test/java/org/wikipedia/media/StateTest.java b/app/src/test/java/org/wikipedia/media/StateTest.java new file mode 100644 index 0000000..55d2fba --- /dev/null +++ b/app/src/test/java/org/wikipedia/media/StateTest.java @@ -0,0 +1,332 @@ +package org.wikipedia.media; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wikipedia.test.TestRunner; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +@RunWith(TestRunner.class) +public class StateTest { + private static final String PATH_A = "http://pathA"; + private static final String PATH_B = "http://pathB"; + + private State subject; + + @Before + public void setUp() { + subject = new State(); + } + + @Test + public void testGetPathConstructor() { + assertThat(subject.getPath(), is((String) null)); + } + + @Test + public void testGetPathDeinit() { + subject.setLoading(PATH_A); + subject.setDeinit(); + assertPath(PATH_A); + } + + @Test + public void testGetPathInit() { + subject.setLoading(PATH_A); + subject.setInit(); + assertPath(PATH_A); + } + + @Test + public void testGetPathLoaded() { + subject.setLoading(PATH_A); + subject.setLoaded(); + assertPath(PATH_A); + } + + @Test + public void testIsDeinitConstructor() { + assertDeinit(true); + } + + @Test + public void testIsDeinitInit() { + subject.setInit(); + assertDeinit(false); + } + + @Test + public void testSetDeinitInit() { + subject.setInit(); + subject.setDeinit(); + assertDeinit(true); + } + + @Test + public void testIsInitConstructor() { + assertInit(false); + } + + @Test + public void testIsInitDeinit() { + subject.setDeinit(); + assertInit(false); + } + + @Test + public void testIsInitInit() { + subject.setInit(); + assertInit(true); + } + + @Test + public void testIsInitLoading() { + subject.setLoading(PATH_A); + assertInit(true); + } + + @Test + public void testIsInitLoaded() { + subject.setLoaded(); + assertInit(true); + } + + @Test + public void testSetInitDeinit() { + subject.setDeinit(); + subject.setInit(); + assertInit(true); + } + + @Test + public void testIsLoadingConstructor() { + assertThat(subject.isLoading(), is(false)); + } + + @Test + public void testIsLoadingLoading() { + subject.setLoading(PATH_A); + assertThat(subject.isLoading(), is(true)); + } + + @Test + public void testIsLoadingLoaded() { + subject.setLoading(PATH_A); + subject.setLoaded(); + assertThat(subject.isLoading(), is(false)); + } + + @Test + public void testIsLoadingLoadAgain() { + subject.setLoading(PATH_A); + subject.setLoaded(); + subject.setLoading(PATH_B); + assertThat(subject.isLoading(), is(true)); + } + + @Test + public void testIsLoadingPathConstructor() { + assertThat(subject.isLoading(PATH_A), is(false)); + } + + @Test + public void testIsLoadingPathLoading() { + subject.setLoading(PATH_A); + assertThat(subject.isLoading(PATH_A), is(true)); + } + + @Test + public void testIsLoadingPathLoaded() { + subject.setLoading(PATH_A); + subject.setLoaded(); + assertThat(subject.isLoading(PATH_A), is(false)); + } + + @Test + public void testIsLoadingPathLoadAgain() { + subject.setLoading(PATH_A); + subject.setLoaded(); + subject.setLoading(PATH_B); + assertThat(subject.isLoading(PATH_B), is(true)); + } + + @Test + public void testIsLoadingPathLoadAgainPreviousPath() { + subject.setLoading(PATH_A); + subject.setLoading(PATH_B); + assertThat(subject.isLoading(PATH_A), is(false)); + } + + @Test + public void testSetLoadingInit() { + subject.setInit(); + subject.setLoading(PATH_A); + assertThat(subject.isLoading(), is(true)); + } + + @Test + public void testIsLoadedConstructor() { + assertLoaded(false); + } + + @Test + public void testIsLoadedLoading() { + subject.setLoading(PATH_A); + assertLoaded(false); + } + + @Test + public void testIsLoadedLoaded() { + subject.setLoading(PATH_A); + subject.setLoaded(); + assertLoaded(true); + } + + @Test + public void testIsLoadedPathConstructor() { + assertLoaded(PATH_A, false); + } + + @Test + public void testIsLoadedPathLoading() { + subject.setLoading(PATH_A); + assertLoaded(PATH_A, false); + } + + @Test + public void testIsLoadedPathLoaded() { + subject.setLoading(PATH_A); + subject.setLoaded(); + assertLoaded(PATH_A, true); + } + + @Test + public void testIsLoadedPathLoadAgain() { + subject.setLoading(PATH_A); + subject.setLoading(PATH_B); + subject.setLoaded(); + assertLoaded(PATH_B, true); + } + + @Test + public void testIsLoadedPathLoadAgainPreviousPath() { + subject.setLoading(PATH_A); + subject.setLoading(PATH_B); + subject.setLoaded(); + assertLoaded(PATH_A, false); + } + + @Test + public void testSetLoadedInit() { + subject.setInit(); + subject.setLoaded(); + assertLoaded(true); + } + + @Test + public void testIsStoppedConstructor() { + assertStopped(true); + } + + @Test + public void testIsStoppedPaused() { + subject.setPaused(); + assertStopped(false); + } + + @Test + public void testIsStoppedDeinit() { + subject.setPlaying(); + subject.setDeinit(); + assertStopped(true); + } + + @Test + public void testSetStopped() { + subject.setPlaying(); + subject.setStopped(); + assertStopped(true); + } + + @Test + public void testIsPlayingConstructor() { + assertPlaying(false); + } + + @Test + public void testIsPlayingPaused() { + subject.setPaused(); + assertPlaying(false); + } + + @Test + public void testIsPlayingDeinit() { + subject.setPlaying(); + subject.setDeinit(); + assertPlaying(false); + } + + @Test + public void testSetPlaying() { + subject.setPlaying(); + assertPlaying(true); + } + + @Test + public void testIsPausedConstructor() { + assertPaused(false); + } + + @Test + public void testIsPausedStopped() { + subject.setStopped(); + assertPaused(false); + } + + @Test + public void testIsPausedDeinit() { + subject.setPaused(); + subject.setDeinit(); + assertPaused(false); + } + + @Test + public void testSetPaused() { + subject.setPaused(); + assertPaused(true); + } + + private void assertInit(boolean init) { + assertThat(subject.isInit(), is(init)); + } + + private void assertLoaded(boolean loaded) { + assertThat(subject.isLoaded(), is(loaded)); + } + + private void assertLoaded(String path, boolean loaded) { + assertThat(subject.isLoaded(path), is(loaded)); + } + + private void assertStopped(boolean stopped) { + assertThat(subject.isStopped(), is(stopped)); + } + + private void assertPlaying(boolean playing) { + assertThat(subject.isPlaying(), is(playing)); + } + + private void assertPaused(boolean paused) { + assertThat(subject.isPaused(), is(paused)); + } + + private void assertPath(String path) { + assertThat(subject.getPath(), is(path)); + } + + private void assertDeinit(boolean deinit) { + assertThat(subject.isDeinit(), is(deinit)); + } +} \ No newline at end of file -- To view, visit https://gerrit.wikimedia.org/r/250903 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I4cf7b0be940085aad1a6931d5eee3215de80488c Gerrit-PatchSet: 1 Gerrit-Project: apps/android/wikipedia Gerrit-Branch: master Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits