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 <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits