package com.mopub.mobileads; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.LayerDrawable; import android.media.MediaPlayer; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.ImageView; import android.widget.VideoView; import com.mopub.common.MoPubBrowser; import com.mopub.common.test.support.SdkTestRunner; import com.mopub.common.util.Dips; import com.mopub.common.util.Drawables; import com.mopub.mobileads.test.support.GestureUtils; import com.mopub.mobileads.util.vast.VastCompanionAd; import com.mopub.mobileads.util.vast.VastVideoConfiguration; import com.mopub.network.Networking; import com.mopub.volley.Request; import com.mopub.volley.RequestQueue; import org.apache.http.HttpRequest; import org.apache.maven.artifact.ant.shaded.ReflectionUtils; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.robolectric.Robolectric; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLocalBroadcastManager; import org.robolectric.shadows.ShadowVideoView; import org.robolectric.tester.org.apache.http.RequestMatcher; import org.robolectric.tester.org.apache.http.TestHttpResponse; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.mopub.common.MoPubBrowser.DESTINATION_URL_KEY; import static com.mopub.common.VolleyRequestMatcher.isUrl; import static com.mopub.common.util.test.support.CommonUtils.assertHttpRequestsMade; import static com.mopub.mobileads.BaseVideoViewController.BaseVideoViewControllerListener; import static com.mopub.mobileads.EventForwardingBroadcastReceiver.ACTION_INTERSTITIAL_CLICK; import static com.mopub.mobileads.EventForwardingBroadcastReceiver.ACTION_INTERSTITIAL_DISMISS; import static com.mopub.mobileads.EventForwardingBroadcastReceiver.ACTION_INTERSTITIAL_FAIL; import static com.mopub.mobileads.EventForwardingBroadcastReceiver.ACTION_INTERSTITIAL_SHOW; import static com.mopub.mobileads.EventForwardingBroadcastReceiver.getHtmlInterstitialIntentFilter; import static com.mopub.mobileads.EventForwardingBroadcastReceiverTest.getIntentForActionAndIdentifier; import static com.mopub.mobileads.VastVideoViewController.DEFAULT_VIDEO_DURATION_FOR_CLOSE_BUTTON; import static com.mopub.mobileads.VastVideoViewController.MAX_VIDEO_DURATION_FOR_CLOSE_BUTTON; import static com.mopub.mobileads.VastVideoViewController.VAST_VIDEO_CONFIGURATION; import static org.fest.assertions.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; 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.stub; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.robolectric.Robolectric.shadowOf; @RunWith(SdkTestRunner.class) public class VastVideoViewControllerTest { public static final int NETWORK_DELAY = 500; private Context context; private Bundle bundle; private long testBroadcastIdentifier; private VastVideoViewController subject; private BaseVideoViewControllerListener baseVideoViewControllerListener; private EventForwardingBroadcastReceiver broadcastReceiver; private int expectedBrowserRequestCode; private String expectedUserAgent; @Mock RequestQueue mockRequestQueue; @Mock private MediaPlayer mockMediaPlayer; @Before public void setUp() throws Exception { Networking.setRequestQueueForTesting(mockRequestQueue); context = new Activity(); bundle = new Bundle(); testBroadcastIdentifier = 1111; broadcastReceiver = mock(EventForwardingBroadcastReceiver.class); baseVideoViewControllerListener = mock(BaseVideoViewControllerListener.class); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setNetworkMediaFileUrl("video_url"); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addStartTrackers(Arrays.asList("start")); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); vastVideoConfiguration.addMidpointTrackers(Arrays.asList("mid")); vastVideoConfiguration.addThirdQuartileTrackers(Arrays.asList("third")); vastVideoConfiguration.addCompleteTrackers(Arrays.asList("complete")); vastVideoConfiguration.addImpressionTrackers(Arrays.asList("imp")); vastVideoConfiguration.setClickThroughUrl("clickThrough"); vastVideoConfiguration.addClickTrackers(Arrays.asList("click_1", "click_2")); VastCompanionAd vastCompanionAd = new VastCompanionAd( 300, 250, "companion_image_url", "companion_click_destination_url", new ArrayList<String>(Arrays.asList("companion_click_tracking_url_1", "companion_click_tracking_url_2")) ); vastVideoConfiguration.setVastCompanionAd(vastCompanionAd); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); expectedBrowserRequestCode = 1; Robolectric.getUiThreadScheduler().pause(); Robolectric.getBackgroundScheduler().pause(); Robolectric.clearPendingHttpResponses(); // Used to give responses to Vast Download Tasks. Robolectric.addHttpResponseRule(new RequestMatcher() { @Override public boolean matches(HttpRequest request) { return true; } }, new TestHttpResponse(200, "body")); ShadowLocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, getHtmlInterstitialIntentFilter()); expectedUserAgent = new WebView(context).getSettings().getUserAgentString(); } @After public void tearDown() throws Exception { Robolectric.getUiThreadScheduler().reset(); Robolectric.getBackgroundScheduler().reset(); ShadowLocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver); } @Test public void constructor_shouldAddVastVideoToolbarToLayout() throws Exception { initializeSubject(); VastVideoToolbar vastVideoToolbar = getVastVideoToolbar(); final ViewGroup.LayoutParams layoutParams = vastVideoToolbar.getLayoutParams(); assertThat(vastVideoToolbar.getParent()).isEqualTo(subject.getLayout()); assertThat(vastVideoToolbar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(layoutParams.width).isEqualTo(MATCH_PARENT); assertThat(layoutParams.height).isEqualTo(Dips.dipsToIntPixels(44, context)); } @Test public void constructor_shouldSetVideoListenersAndVideoPath() throws Exception { initializeSubject(); ShadowVideoView videoView = shadowOf(subject.getVideoView()); assertThat(videoView.getOnCompletionListener()).isNotNull(); assertThat(videoView.getOnErrorListener()).isNotNull(); assertThat(videoView.getOnTouchListener()).isNotNull(); assertThat(videoView.getOnPreparedListener()).isNotNull(); assertThat(videoView.getVideoPath()).isEqualTo("disk_video_path"); assertThat(subject.getVideoView().hasFocus()).isTrue(); } @Test public void constructor_shouldNotChangeCloseButtonDelay() throws Exception { initializeSubject(); assertThat(subject.getShowCloseButtonDelay()).isEqualTo(DEFAULT_VIDEO_DURATION_FOR_CLOSE_BUTTON); } @Test public void constructor_shouldAddThatchedBackgroundWithGradientToLayout() throws Exception { initializeSubject(); ViewGroup viewGroup = subject.getLayout(); LayerDrawable layerDrawable = (LayerDrawable) viewGroup.getBackground(); assertThat(layerDrawable.getDrawable(0)).isEqualTo(Drawables.THATCHED_BACKGROUND.createDrawable( context)); assertThat(layerDrawable.getDrawable(1)).isEqualTo( new GradientDrawable( GradientDrawable.Orientation.TOP_BOTTOM, new int[]{Color.argb(0, 0, 0, 0), Color.argb(255, 0, 0, 0)}) ); } @Test public void constructor_withMissingVastVideoConfiguration_shouldThrowIllegalStateException() throws Exception { bundle.clear(); try { initializeSubject(); fail("VastVideoViewController didn't throw IllegalStateException"); } catch (IllegalStateException e) { // pass } } @Test public void constructor_withNullVastVideoConfigurationDiskMediaFileUrl_shouldThrowIllegalStateException() throws Exception { bundle.putSerializable(VAST_VIDEO_CONFIGURATION, new VastVideoConfiguration()); try { initializeSubject(); fail("VastVideoViewController didn't throw IllegalStateException"); } catch (IllegalStateException e) { // pass } } @Test public void onCreate_withCompanionAd_shouldDownloadCompanionAd() throws Exception { initializeSubject(); final ImageView imageView = subject.getCompanionAdImageView(); assertThat(imageView.getDrawable()).isNull(); subject.onCreate(); Robolectric.getBackgroundScheduler().unPause(); Robolectric.getUiThreadScheduler().unPause(); Thread.sleep(NETWORK_DELAY); assertThat(shadowOf(((BitmapDrawable) imageView.getDrawable()).getBitmap()).getCreatedFromBytes()).isEqualTo("body".getBytes()); } @Test public void onCreate_shouldFireImpressionTracker() throws Exception { initializeSubject(); subject.onCreate(); verify(mockRequestQueue).add(argThat(isUrl("imp"))); } @Test public void onCreate_shouldBroadcastInterstitialShow() throws Exception { Intent expectedIntent = getIntentForActionAndIdentifier(ACTION_INTERSTITIAL_SHOW, testBroadcastIdentifier); initializeSubject(); Robolectric.getUiThreadScheduler().unPause(); subject.onCreate(); verify(broadcastReceiver).onReceive(any(Context.class), eq(expectedIntent)); } @Test public void onComplete_withNullDownloadResponse_shouldNotSetCompanionAdImageBitmap() throws Exception { initializeSubject(); final ImageView imageView = subject.getCompanionAdImageView(); assertThat(imageView.getDrawable()).isNull(); subject.onComplete("url", null); assertThat(imageView.getDrawable()).isNull(); } @Test public void onClick_withCompanionAd_shouldFireCompanionAdClickTrackersAndStartMoPubBrowser() throws Exception { initializeSubject(); final ImageView imageView = subject.getCompanionAdImageView(); assertThat(imageView.performClick()).isFalse(); subject.onCreate(); Robolectric.getBackgroundScheduler().unPause(); Robolectric.getUiThreadScheduler().unPause(); Thread.sleep(NETWORK_DELAY); assertThat(imageView.performClick()).isTrue(); // This request is still made by the older http stack. assertHttpRequestsMade(expectedUserAgent, "companion_image_url"); verify(mockRequestQueue).add(argThat(isUrl("companion_click_tracking_url_1"))); verify(mockRequestQueue).add(argThat(isUrl("companion_click_tracking_url_2"))); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(baseVideoViewControllerListener).onStartActivityForResult( eq(MoPubBrowser.class), eq(expectedBrowserRequestCode), bundleCaptor.capture() ); assertThat(bundleCaptor.getValue().get(DESTINATION_URL_KEY)).isEqualTo("companion_click_destination_url"); } @Test public void onDestroy_shouldBroadcastInterstitialDismiss() throws Exception { Intent expectedIntent = getIntentForActionAndIdentifier(ACTION_INTERSTITIAL_DISMISS, testBroadcastIdentifier); initializeSubject(); subject.onDestroy(); Robolectric.getUiThreadScheduler().unPause(); verify(broadcastReceiver).onReceive(any(Context.class), eq(expectedIntent)); } @Test public void onActivityResult_shouldCallFinish() throws Exception { final int expectedResultCode = Activity.RESULT_OK; initializeSubject(); subject.onActivityResult(expectedBrowserRequestCode, expectedResultCode, null); verify(baseVideoViewControllerListener).onFinish(); } @Test public void onActivityResult_withIncorrectRequestCode_shouldNotCallFinish() throws Exception { final int incorrectRequestCode = 1000; final int expectedResultCode = Activity.RESULT_OK; initializeSubject(); subject.onActivityResult(incorrectRequestCode, expectedResultCode, null); verify(baseVideoViewControllerListener, never()).onFinish(); } @Test public void onActivityResult_withIncorrectResultCode_shouldNotCallFinish() throws Exception { final int incorrectResultCode = Activity.RESULT_CANCELED; initializeSubject(); subject.onActivityResult(expectedBrowserRequestCode, incorrectResultCode, null); verify(baseVideoViewControllerListener, never()).onFinish(); } @Test public void onTouch_withTouchUp_whenVideoLessThan16Seconds_andClickBeforeEnd_shouldDoNothing() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(15999); stub(mockMediaPlayer.getCurrentPosition()).toReturn(15990); initializeSubject(); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); Robolectric.getUiThreadScheduler().unPause(); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); Intent nextStartedActivity = Robolectric.getShadowApplication().getNextStartedActivity(); assertThat(nextStartedActivity).isNull(); } @Test public void onTouch_withTouchUp_whenVideoLessThan16Seconds_andClickAfterEnd_shouldStartMoPubBrowser() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(15999); stub(mockMediaPlayer.getCurrentPosition()).toReturn(16000); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); Robolectric.getUiThreadScheduler().unPause(); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(baseVideoViewControllerListener).onStartActivityForResult( eq(MoPubBrowser.class), eq(expectedBrowserRequestCode), bundleCaptor.capture() ); assertThat(bundleCaptor.getValue().get(DESTINATION_URL_KEY)).isEqualTo("clickThrough"); } @Test public void onTouch_withTouchUp_whenVideoLongerThan16Seconds_andClickBefore5Seconds_shouldDoNothing() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100000); stub(mockMediaPlayer.getCurrentPosition()).toReturn(4999); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); Robolectric.getUiThreadScheduler().unPause(); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); Intent nextStartedActivity = Robolectric.getShadowApplication().getNextStartedActivity(); assertThat(nextStartedActivity).isNull(); } @Test public void onTouch_withTouchUp_whenVideoLongerThan16Seconds_andClickAfter5Seconds_shouldStartMoPubBrowser() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100000); stub(mockMediaPlayer.getCurrentPosition()).toReturn(5001); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); Robolectric.getUiThreadScheduler().unPause(); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(baseVideoViewControllerListener).onStartActivityForResult( eq(MoPubBrowser.class), eq(expectedBrowserRequestCode), bundleCaptor.capture() ); assertThat(bundleCaptor.getValue().get(DESTINATION_URL_KEY)).isEqualTo("clickThrough"); } @Test public void onTouch_whenCloseButtonVisible_shouldPingClickThroughTrackers() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addClickTrackers(Arrays.asList("click_1", "click_2")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.setCloseButtonVisible(true); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); verify(mockRequestQueue).add(argThat(isUrl("click_1"))); verify(mockRequestQueue).add(argThat(isUrl("click_2"))); } @Test public void onTouch_whenCloseButtonNotVisible_shouldNotPingClickThroughTrackers() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addClickTrackers(Arrays.asList("click_1", "click_2")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.setCloseButtonVisible(false); getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); assertThat(Robolectric.httpRequestWasMade()).isFalse(); } @Test public void onTouch_withNullBaseVideoViewListener_andActionTouchUp_shouldReturnTrueAndNotBlowUp() throws Exception { subject = new VastVideoViewController(context, bundle, testBroadcastIdentifier, null); boolean result = getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionUp(0, 0)); // pass assertThat(result).isTrue(); } @Test public void onTouch_withActionTouchDown_shouldConsumeMotionEvent() throws Exception { initializeSubject(); boolean result = getShadowVideoView().getOnTouchListener().onTouch(null, GestureUtils.createActionDown(0, 0)); assertThat(result).isTrue(); } @Test public void onPrepared_whenDurationIsLessThanMaxVideoDurationForCloseButton_shouldSetShowCloseButtonDelayToDuration() throws Exception { initializeSubject(); stub(mockMediaPlayer.getDuration()).toReturn(1000); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); assertThat(subject.getShowCloseButtonDelay()).isEqualTo(1000); } @Test public void onPrepared_whenDurationIsGreaterThanMaxVideoDurationForCloseButton_shouldNotSetShowCloseButtonDelay() throws Exception { initializeSubject(); stub(mockMediaPlayer.getDuration()).toReturn(MAX_VIDEO_DURATION_FOR_CLOSE_BUTTON + 1); setMockMediaPlayer(mockMediaPlayer); getShadowVideoView().getOnPreparedListener().onPrepared(null); assertThat(subject.getShowCloseButtonDelay()).isEqualTo(DEFAULT_VIDEO_DURATION_FOR_CLOSE_BUTTON); } @Test public void onCompletion_shouldMarkVideoAsFinished() throws Exception { initializeSubject(); getShadowVideoView().getOnCompletionListener().onCompletion(null); assertThat(subject.isVideoFinishedPlaying()).isTrue(); } @Test public void onCompletion_whenFinalMarkHit_whenNoPlaybackErrors_shouldPingCompletionTrackersOnlyOnce() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addCompleteTrackers(Arrays.asList("complete_1", "complete_2")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.setFinalMarkHit(); getShadowVideoView().getOnCompletionListener().onCompletion(null); verify(mockRequestQueue).add(argThat(isUrl("complete_1"))); verify(mockRequestQueue).add(argThat(isUrl("complete_2"))); // Completion trackers should still only be hit once getShadowVideoView().getOnCompletionListener().onCompletion(null); verify(mockRequestQueue).add(argThat(isUrl("complete_1"))); verify(mockRequestQueue).add(argThat(isUrl("complete_2"))); } @Test public void onCompletion_whenFinalMarkNotHit_shouldNotPingCompletionTrackers() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addCompleteTrackers(Arrays.asList("complete_1", "complete_2")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); // explicitly do not call subject.setFinalMarkHit(); getShadowVideoView().getOnCompletionListener().onCompletion(null); verify(mockRequestQueue, never()).add(argThat(isUrl("complete_1"))); verify(mockRequestQueue, never()).add(argThat(isUrl("complete_2"))); } @Test public void onCompletion_whenPlaybackError_shouldNotPingCompletionTrackers() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addCompleteTrackers(Arrays.asList("complete_1", "complete_2")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.setVideoError(); getShadowVideoView().getOnCompletionListener().onCompletion(null); verify(mockRequestQueue, never()).add(argThat(isUrl("complete_1"))); verify(mockRequestQueue, never()).add(argThat(isUrl("complete_2"))); } @Test public void onCompletion_shouldPreventOnResumeFromStartingVideo() throws Exception { initializeSubject(); getShadowVideoView().getOnCompletionListener().onCompletion(null); subject.onResume(); assertThat(getShadowVideoView().isPlaying()).isFalse(); } @Test public void onCompletion_shouldStopProgressChecker() throws Exception { initializeSubject(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); getShadowVideoView().getOnCompletionListener().onCompletion(null); assertThat(subject.getIsVideoProgressShouldBeChecked()).isFalse(); } @Test public void onCompletion_shouldDisplayCompanionAdIfAvailable() throws Exception { initializeSubject(); subject.onCreate(); Robolectric.getBackgroundScheduler().unPause(); Robolectric.getUiThreadScheduler().unPause(); Thread.sleep(NETWORK_DELAY); final ImageView imageView = subject.getCompanionAdImageView(); assertThat(subject.getVideoView().getVisibility()).isEqualTo(View.VISIBLE); assertThat(imageView.getVisibility()).isEqualTo(View.INVISIBLE); getShadowVideoView().getOnCompletionListener().onCompletion(null); assertThat(subject.getVideoView().getVisibility()).isEqualTo(View.GONE); assertThat(imageView.getVisibility()).isEqualTo(View.VISIBLE); assertThat(shadowOf(((BitmapDrawable) imageView.getDrawable()).getBitmap()).getCreatedFromBytes()).isEqualTo("body".getBytes()); } @Test public void onCompletion_shouldShowThatchedBackground() throws Exception { initializeSubject(); final ImageView imageView = subject.getCompanionAdImageView(); assertThat(subject.getVideoView().getVisibility()).isEqualTo(View.VISIBLE); assertThat(imageView.getVisibility()).isEqualTo(View.INVISIBLE); getShadowVideoView().getOnCompletionListener().onCompletion(null); assertThat(subject.getVideoView().getVisibility()).isEqualTo(View.GONE); assertThat(imageView.getVisibility()).isEqualTo(View.INVISIBLE); } @Test public void onError_shouldFireVideoErrorAndReturnFalse() throws Exception { initializeSubject(); Intent expectedIntent = getIntentForActionAndIdentifier(ACTION_INTERSTITIAL_FAIL, testBroadcastIdentifier); boolean result = getShadowVideoView().getOnErrorListener().onError(null, 0, 0); Robolectric.getUiThreadScheduler().unPause(); assertThat(result).isFalse(); verify(broadcastReceiver).onReceive(any(Context.class), eq(expectedIntent)); assertThat(subject.getVideoError()).isTrue(); } @Test public void onError_shouldStopProgressChecker() throws Exception { initializeSubject(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); getShadowVideoView().getOnErrorListener().onError(null, 0, 0); assertThat(subject.getIsVideoProgressShouldBeChecked()).isFalse(); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Test public void onError_withVideoFilePermissionErrorBelowJellyBean_shouldRetryPlayingTheVideo() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(getShadowVideoView().getCurrentVideoState()).isEqualTo(-1); assertThat(subject.getVideoRetries()).isEqualTo(0); getShadowVideoView().getOnErrorListener().onError(new MediaPlayer(), 1, Integer.MIN_VALUE); assertThat(getShadowVideoView().isPlaying()).isTrue(); assertThat(subject.getVideoRetries()).isEqualTo(1); file.delete(); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Test public void retryMediaPlayer_withVideoFilePermissionErrorAndBelowJellyBean_shouldReturnTrue() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(subject.getVideoRetries()).isEqualTo(0); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isTrue(); assertThat(subject.getVideoRetries()).isEqualTo(1); file.delete(); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Test public void retryMediaPlayer_shouldNotRunMoreThanOnce() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(subject.getVideoRetries()).isEqualTo(0); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isTrue(); assertThat(subject.getVideoRetries()).isEqualTo(1); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isFalse(); assertThat(subject.getVideoRetries()).isEqualTo(1); file.delete(); } @Config(reportSdk = VERSION_CODES.JELLY_BEAN) @Test public void retryMediaPlayer_withAndroidVersionAboveJellyBean_shouldReturnFalse() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(subject.getVideoRetries()).isEqualTo(0); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isFalse(); assertThat(subject.getVideoRetries()).isEqualTo(0); file.delete(); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH) @Test public void retryMediaPlayer_withOtherVideoError_shouldReturnFalse() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(subject.getVideoRetries()).isEqualTo(0); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 2, Integer.MIN_VALUE)).isFalse(); assertThat(subject.getVideoRetries()).isEqualTo(0); file.delete(); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH) @Test public void retryMediaPlayer_withExceptionThrown_shouldReturnFalseAndIncrementRetryCount() throws Exception { File file = new File("disk_video_path"); if (file.exists()){ assertThat(file.delete()).isTrue(); } initializeSubject(); assertThat(subject.getVideoRetries()).isEqualTo(0); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isFalse(); assertThat(subject.getVideoRetries()).isEqualTo(1); } @Test public void handleClick_shouldMakeRequestsToClickTrackingUrls() { initializeSubject(); subject.handleClick(Arrays.asList("clicktracker1", "clicktracker2"), "clickthrough"); verify(mockRequestQueue).add(argThat(isUrl("clicktracker1"))); verify(mockRequestQueue).add(argThat(isUrl("clicktracker2"))); } @Test public void handleClick_withNullClickTrackers_shouldNotThrowAnException() { initializeSubject(); subject.handleClick(null, "clickthrough"); // pass } @Test public void handleClick_withNullClickThroughUrl_shouldNotBroadcastClickOrOpenNewActivity() { Intent expectedIntent = getIntentForActionAndIdentifier(ACTION_INTERSTITIAL_CLICK, testBroadcastIdentifier); initializeSubject(); subject.handleClick(Arrays.asList("clicktracker"), null); Robolectric.getUiThreadScheduler().unPause(); verify(broadcastReceiver, never()).onReceive(any(Context.class), eq(expectedIntent)); assertThat(Robolectric.getShadowApplication().getNextStartedActivity()).isNull(); } @Test public void handleClick_withMoPubNativeBrowserClickThroughUrl_shouldOpenExternalBrowser() { initializeSubject(); subject.handleClick(Arrays.asList("clicktracker"), "mopubnativebrowser://navigate?url=http%3A%2F%2Fwww.mopub.com"); Intent intent = Robolectric.getShadowApplication().getNextStartedActivity(); assertThat(intent.getDataString()).isEqualTo("http://www.mopub.com"); assertThat(intent.getAction()).isEqualTo(Intent.ACTION_VIEW); } @Test public void handleClick_withMalformedMoPubNativeBrowserClickThroughUrl_shouldNotOpenANewActivity() { initializeSubject(); // url2 is an invalid query parameter subject.handleClick(Arrays.asList("clicktracker"), "mopubnativebrowser://navigate?url2=http%3A%2F%2Fwww.mopub.com"); assertThat(Robolectric.getShadowApplication().getNextStartedActivity()).isNull(); } @Test public void videoProgressCheckerRunnableRun_shouldFireOffAllProgressTrackers() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(9001); stub(mockMediaPlayer.getCurrentPosition()).toReturn(9002); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); vastVideoConfiguration.addMidpointTrackers(Arrays.asList("second")); vastVideoConfiguration.addThirdQuartileTrackers(Arrays.asList("third")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); // this runs the videoProgressChecker Robolectric.getUiThreadScheduler().unPause(); verify(mockRequestQueue).add(argThat(isUrl("first"))); verify(mockRequestQueue).add(argThat(isUrl("second"))); verify(mockRequestQueue).add(argThat(isUrl("third"))); } @Test public void videoProgressCheckerRunnableRun_whenDurationIsInvalid_shouldNotMakeAnyNetworkCalls() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(0); stub(mockMediaPlayer.getCurrentPosition()).toReturn(100); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); setMockMediaPlayer(mockMediaPlayer); subject.onResume(); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().runOneTask(); // make sure the repeated task hasn't run yet assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); verifyZeroInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_whenCurrentTimeLessThanTwoSeconds_shouldNotFireStartTracker() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addStartTrackers(Arrays.asList("start")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); stub(mockMediaPlayer.getDuration()).toReturn(100000); stub(mockMediaPlayer.getCurrentPosition()).toReturn(1999); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().runOneTask(); // make sure the repeated task hasn't run yet assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); // Since it has not yet been a second, we expect that the start tracker has not been fired verifyZeroInteractions(mockRequestQueue); // run checker another time assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().runOneTask(); verifyZeroInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_whenCurrentTimeGreaterThanTwoSeconds_shouldFireStartTracker() throws Exception { VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addStartTrackers(Arrays.asList("start")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); stub(mockMediaPlayer.getDuration()).toReturn(100000); stub(mockMediaPlayer.getCurrentPosition()).toReturn(2000); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().unPause(); verify(mockRequestQueue).add(argThat(isUrl("start"))); // run checker another time assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().runOneTask(); verifyNoMoreInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_whenProgressIsPastFirstQuartile_shouldOnlyPingFirstQuartileTrackersOnce() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100); stub(mockMediaPlayer.getCurrentPosition()).toReturn(26); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().unPause(); verify(mockRequestQueue).add(argThat(isUrl("first"))); // run checker another time Robolectric.getUiThreadScheduler().runOneTask(); verifyNoMoreInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_whenProgressIsPastMidQuartile_shouldPingFirstQuartileTrackers_andMidQuartileTrackersBothOnlyOnce() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100); stub(mockMediaPlayer.getCurrentPosition()).toReturn(51); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); vastVideoConfiguration.addMidpointTrackers(Arrays.asList("second")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().unPause(); verify(mockRequestQueue).add(argThat(isUrl("first"))); verify(mockRequestQueue).add(argThat(isUrl("second"))); Robolectric.getUiThreadScheduler().runOneTask(); verifyNoMoreInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_whenProgressIsPastThirdQuartile_shouldPingFirstQuartileTrackers_andMidQuartileTrackers_andThirdQuartileTrackersAllOnlyOnce() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100); stub(mockMediaPlayer.getCurrentPosition()).toReturn(76); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); vastVideoConfiguration.addMidpointTrackers(Arrays.asList("second")); vastVideoConfiguration.addThirdQuartileTrackers(Arrays.asList("third")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(Robolectric.getUiThreadScheduler().enqueuedTaskCount()).isEqualTo(1); Robolectric.getUiThreadScheduler().unPause(); verify(mockRequestQueue).add(argThat(isUrl("first"))); verify(mockRequestQueue).add(argThat(isUrl("second"))); verify(mockRequestQueue).add(argThat(isUrl("third"))); Robolectric.getUiThreadScheduler().runOneTask(); verifyNoMoreInteractions(mockRequestQueue); } @Test public void videoProgressCheckerRunnableRun_asVideoPlays_shouldPingAllThreeTrackersIndividuallyOnce() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(100); VastVideoConfiguration vastVideoConfiguration = new VastVideoConfiguration(); vastVideoConfiguration.setDiskMediaFileUrl("disk_video_path"); vastVideoConfiguration.addFirstQuartileTrackers(Arrays.asList("first")); vastVideoConfiguration.addMidpointTrackers(Arrays.asList("second")); vastVideoConfiguration.addThirdQuartileTrackers(Arrays.asList("third")); bundle.putSerializable(VAST_VIDEO_CONFIGURATION, vastVideoConfiguration); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); // before any trackers are fired fastForwardMediaPlayerAndAssertRequestMade(1); fastForwardMediaPlayerAndAssertRequestMade(24); // after it hits first tracker fastForwardMediaPlayerAndAssertRequestMade(26, "first"); // before mid quartile is hit fastForwardMediaPlayerAndAssertRequestMade(49); // after it hits mid trackers fastForwardMediaPlayerAndAssertRequestMade(51, "second"); // before third quartile is hit fastForwardMediaPlayerAndAssertRequestMade(74); // after third quartile is hit fastForwardMediaPlayerAndAssertRequestMade(76, "third"); // way after third quartile is hit fastForwardMediaPlayerAndAssertRequestMade(99); } @Test public void videoProgressCheckerRunnableRun_whenCurrentPositionIsGreaterThanShowCloseButtonDelay_shouldShowCloseButton() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(5002); stub(mockMediaPlayer.getCurrentPosition()).toReturn(5001); initializeSubject(); subject.onResume(); setMockMediaPlayer(mockMediaPlayer); assertThat(subject.isShowCloseButtonEventFired()).isFalse(); Robolectric.getUiThreadScheduler().unPause(); assertThat(subject.isShowCloseButtonEventFired()).isTrue(); } @Test public void onPause_shouldStopProgressChecker() throws Exception { initializeSubject(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); subject.onPause(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isFalse(); subject.onPause(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isFalse(); } @Test public void onResume_shouldStartVideoProgressCheckerOnce() throws Exception { initializeSubject(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); subject.onPause(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isFalse(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); subject.onResume(); assertThat(subject.getIsVideoProgressShouldBeChecked()).isTrue(); } @Test public void onResume_shouldSetVideoViewStateToStarted() throws Exception { initializeSubject(); subject.onResume(); assertThat(getShadowVideoView().getCurrentVideoState()).isEqualTo(ShadowVideoView.START); assertThat(getShadowVideoView().getPrevVideoState()).isNotEqualTo(ShadowVideoView.START); } @Config(reportSdk = VERSION_CODES.ICE_CREAM_SANDWICH_MR1) @Test public void onResume_shouldResetVideoRetryCountToZero() throws Exception { File file = new File("disk_video_path"); file.createNewFile(); initializeSubject(); assertThat(subject.retryMediaPlayer(new MediaPlayer(), 1, Integer.MIN_VALUE)).isTrue(); assertThat(subject.getVideoRetries()).isEqualTo(1); subject.onResume(); assertThat(subject.getVideoRetries()).isEqualTo(0); file.delete(); } @Ignore("pending") @Test public void onResume_shouldSeekToPrePausedPosition() throws Exception { stub(mockMediaPlayer.getDuration()).toReturn(10000); stub(mockMediaPlayer.getCurrentPosition()).toReturn(7000); initializeSubject(); setMockMediaPlayer(mockMediaPlayer); final VideoView videoView = spy(subject.getVideoView()); subject.onPause(); stub(mockMediaPlayer.getCurrentPosition()).toReturn(1000); subject.onResume(); verify(videoView).seekTo(eq(7000)); } @Test public void backButtonEnabled_shouldDefaultToFalse() throws Exception { initializeSubject(); assertThat(subject.backButtonEnabled()).isFalse(); } @Test public void backButtonEnabled_whenCloseButtonIsVisible_shouldReturnTrue() throws Exception { initializeSubject(); subject.setCloseButtonVisible(true); assertThat(subject.backButtonEnabled()).isTrue(); } private void initializeSubject() { subject = new VastVideoViewController(context, bundle, testBroadcastIdentifier, baseVideoViewControllerListener); } private void setMockMediaPlayer(final MediaPlayer mockMediaPlayer) throws IllegalAccessException { final VideoView videoView = subject.getVideoView(); ReflectionUtils.setVariableValueInObject(videoView, "mMediaPlayer", mockMediaPlayer); int state = (Integer) ReflectionUtils.getValueIncludingSuperclasses("STATE_PLAYING", videoView); ReflectionUtils.setVariableValueInObject(videoView, "mCurrentState", state); } private void fastForwardMediaPlayerAndAssertRequestMade(int time, String... urls) throws Exception { stub(mockMediaPlayer.getCurrentPosition()).toReturn(time); Robolectric.getUiThreadScheduler().unPause(); Robolectric.getBackgroundScheduler().unPause(); Thread.sleep(NETWORK_DELAY); for (String url : urls) { verify(mockRequestQueue).add(argThat(isUrl(url))); } Robolectric.getFakeHttpLayer().clearRequestInfos(); } private VastVideoToolbar getVastVideoToolbar() { final ViewGroup layout = subject.getLayout(); for (int i = 0; i < layout.getChildCount(); i++) { final View child = layout.getChildAt(i); if (child instanceof VastVideoToolbar) { return (VastVideoToolbar) child; } } fail("Unable to find VastVideoToolbar in view hierarchy."); return null; } private ShadowVideoView getShadowVideoView() { return shadowOf(subject.getVideoView()); } }