diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63873ff7f..5add0cbcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## Version 4.19.0 (December 11, 2017)
+- Updated Facebook Audience Network adapters to 4.26.1.
+- Updated Flurry adapters to 8.1.0.
+- Updated Millennial rewarded ads adapters to 6.6.1.
+- Fixed a potential crash for native video ads when attempting to blur the last video frame.
+- Fixed a duplicate on loaded callback for some rewarded ads.
+
## Version 4.18.0 (November 1, 2017)
- Updated the SDK compile version to 26. Android API 26 artifacts live in the new Google maven repository `maven { url 'https://maven.google.com' }`. See [this article](https://developer.android.com/about/versions/oreo/android-8.0-migration.html) for more information about using Android API 26.
- Fixed MoPub in-app browser's back and forward button icons.
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 000000000..0f179eab2
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,22 @@
+#!/usr/bin/env groovy
+pipeline {
+ agent any
+ environment {
+ ANDROID_HOME = '/Users/jenkins/Library/Android/sdk'
+ }
+ stages {
+ stage('Build') {
+ steps {
+ sh './gradlew clean build'
+ }
+ }
+ }
+ post {
+ success {
+ hipchatSend message: "${env.JOB_NAME} #${env.BUILD_NUMBER} has succeeded.", color: 'GREEN'
+ }
+ failure {
+ hipchatSend message: "Attention @here ${env.JOB_NAME} #${env.BUILD_NUMBER} has failed.", color: 'RED'
+ }
+ }
+}
diff --git a/README.md b/README.md
index c98dd9bc3..a262d86f7 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ The MoPub SDK is available via:
}
dependencies {
- compile('com.mopub:mopub-sdk:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk:4.19.0@aar') {
transitive = true
}
}
@@ -61,27 +61,27 @@ The MoPub SDK is available via:
// ... other project dependencies
// For banners
- compile('com.mopub:mopub-sdk-banner:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk-banner:4.19.0@aar') {
transitive = true
}
// For interstitials
- compile('com.mopub:mopub-sdk-interstitial:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk-interstitial:4.19.0@aar') {
transitive = true
}
// For rewarded videos. This will automatically also include interstitials
- compile('com.mopub:mopub-sdk-rewardedvideo:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk-rewardedvideo:4.19.0@aar') {
transitive = true
}
// For native static (images).
- compile('com.mopub:mopub-sdk-native-static:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk-native-static:4.19.0@aar') {
transitive = true
}
// For native video. This will automatically also include native static
- compile('com.mopub:mopub-sdk-native-video:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk-native-video:4.19.0@aar') {
transitive = true
}
}
@@ -109,24 +109,18 @@ The MoPub SDK is available via:
## New in this Version
Please view the [changelog](https://github.com/mopub/mopub-android-sdk/blob/master/CHANGELOG.md) for a complete list of additions, fixes, and enhancements in the latest release.
-- Updated the SDK compile version to 26. Android API 26 artifacts live in the new Google maven repository `maven { url 'https://maven.google.com' }`. See [this article](https://developer.android.com/about/versions/oreo/android-8.0-migration.html) for more information about using Android API 26.
-- Fixed MoPub in-app browser's back and forward button icons.
-- Updated AdMob adapters to 11.4.0.
-- Updated Chartboost adapters to 7.0.1.
-- Updated Facebook Audience Network adapters to 4.26.0.
-- Updated Millennial to 6.6.1.
-- Updated TapJoy adapters to 11.11.0.
-- Updated Unity Ads adapters to 2.1.1.
-- Updated Vungle adapters to 5.3.0.
+- Updated Facebook Audience Network adapters to 4.26.1.
+- Updated Flurry adapters to 8.1.0.
+- Updated Millennial rewarded ads adapters to 6.6.1.
## Requirements
- Android 4.1 (API Version 16) and up (**Updated in 4.12.0**)
-- android-support-v4.jar, r23 (**Updated in 4.4.0**)
-- android-support-annotations.jar, r23 (**Updated in 4.4.0**)
-- android-support-v7-recyclerview.jar, r23 (**Updated in 4.4.0**)
+- android-support-v4.jar, r26 (**Updated in 4.18.0**)
+- android-support-annotations.jar, r26 (**Updated in 4.18.0**)
+- android-support-v7-recyclerview.jar, r26 (**Updated in 4.18.0**)
- MoPub Volley Library (mopub-volley-1.1.0.jar - available on JCenter) (**Updated in 3.6.0**)
-- **Recommended** Google Play Services 9.4.0
+- **Recommended** Google Play Services 11.4.0
## Upgrading from 4.15.0 and Prior
In 4.16.0, dependencies were added to viewability libraries provided by AVID and Moat. Apps upgrading from previous versions must add
@@ -149,7 +143,7 @@ Update to the following to exclude one or both viewability vendors:
```
dependencies {
- compile('com.mopub:mopub-sdk:4.18.0@aar') {
+ compile('com.mopub:mopub-sdk:4.19.0@aar') {
transitive = true
exclude module: 'libAvid-mopub' // To exclude AVID
exclude module: 'moat-mobile-app-kit' // To exclude Moat
diff --git a/extras/src/com/mopub/mobileads/MillennialRewardedVideo.java b/extras/src/com/mopub/mobileads/MillennialRewardedVideo.java
new file mode 100644
index 000000000..ca906ba1b
--- /dev/null
+++ b/extras/src/com/mopub/mobileads/MillennialRewardedVideo.java
@@ -0,0 +1,361 @@
+package com.mopub.mobileads;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.millennialmedia.AppInfo;
+import com.millennialmedia.CreativeInfo;
+import com.millennialmedia.InterstitialAd;
+import com.millennialmedia.InterstitialAd.InterstitialErrorStatus;
+import com.millennialmedia.InterstitialAd.InterstitialListener;
+import com.millennialmedia.MMException;
+import com.millennialmedia.MMLog;
+import com.millennialmedia.MMSDK;
+import com.millennialmedia.XIncentivizedEventListener;
+import com.mopub.common.BaseLifecycleListener;
+import com.mopub.common.LifecycleListener;
+import com.mopub.common.MoPub;
+import com.mopub.common.MoPubReward;
+
+import java.util.Map;
+
+
+/**
+ * Compatible with version 6.6 of the Millennial Media SDK.
+ */
+
+@SuppressWarnings("unused")
+final class MillennialRewardedVideo extends CustomEventRewardedVideo {
+
+ private static final String TAG = MillennialRewardedVideo.class.getSimpleName();
+ public static final String DCN_KEY = "dcn";
+ public static final String APID_KEY = "adUnitID";
+
+ private InterstitialAd millennialInterstitial;
+ private MillennialRewardedVideoListener millennialRewardedVideoListener = new MillennialRewardedVideoListener();
+ private Context context;
+ private String apid = null;
+
+ static {
+ Log.i(TAG, "Millennial Media Adapter Version: " + MillennialUtils.VERSION);
+ }
+
+
+ public CreativeInfo getCreativeInfo() {
+
+ if (millennialInterstitial == null) {
+ return null;
+ }
+
+ return millennialInterstitial.getCreativeInfo();
+ }
+
+
+ @Nullable
+ @Override
+ protected CustomEventRewardedVideoListener getVideoListenerForSdk() {
+
+ return millennialRewardedVideoListener;
+ }
+
+
+ @Nullable
+ @Override
+ protected LifecycleListener getLifecycleListener() {
+
+ return new BaseLifecycleListener();
+ }
+
+
+ @NonNull
+ @Override
+ protected String getAdNetworkId() {
+
+ return (apid == null) ? "" : apid;
+ }
+
+
+ @Override
+ protected void onInvalidate() {
+
+ if (millennialInterstitial != null) {
+ millennialInterstitial.destroy();
+ millennialInterstitial = null;
+ apid = null;
+ }
+ }
+
+
+ @Override
+ protected boolean checkAndInitializeSdk(@NonNull Activity launcherActivity,
+ @NonNull Map localExtras, @NonNull Map serverExtras) throws Exception {
+
+ if (!MillennialUtils.initSdk(launcherActivity)) {
+ Log.e(TAG, "MM SDK must be initialized with an Activity or Application context.");
+
+ return false;
+ }
+
+ return true;
+ }
+
+
+ @Override
+ protected void loadWithSdkInitialized(@NonNull Activity activity, @NonNull Map localExtras,
+ @NonNull Map serverExtras) throws Exception {
+
+ this.context = activity.getApplicationContext();
+ apid = serverExtras.get(APID_KEY);
+ String dcn = serverExtras.get(DCN_KEY);
+
+ if (MillennialUtils.isEmpty(apid)) {
+ Log.e(TAG, "Invalid extras-- Be sure you have a placement ID specified.");
+ MoPubRewardedVideoManager.onRewardedVideoLoadFailure(MillennialRewardedVideo.class, "",
+ MoPubErrorCode.ADAPTER_CONFIGURATION_ERROR);
+
+ return;
+ }
+
+ // Add DCN support
+ AppInfo ai = new AppInfo().setMediator("mopubsdk").setSiteId(dcn);
+ try {
+ MMSDK.setAppInfo(ai);
+ /* If MoPub gets location, so do we. */
+ MMSDK.setLocationEnabled(MoPub.getLocationAwareness() != MoPub.LocationAwareness.DISABLED);
+
+ millennialInterstitial = InterstitialAd.createInstance(apid);
+ millennialInterstitial.setListener(millennialRewardedVideoListener);
+ millennialInterstitial.xSetIncentivizedListener(millennialRewardedVideoListener);
+ millennialInterstitial.load(activity, null);
+
+ } catch (MMException e) {
+ Log.e(TAG, "An exception occurred loading an InterstitialAd", e);
+ MoPubRewardedVideoManager
+ .onRewardedVideoLoadFailure(MillennialRewardedVideo.class, apid, MoPubErrorCode.INTERNAL_ERROR);
+ }
+ }
+
+
+ @Override
+ protected boolean hasVideoAvailable() {
+
+ return ((millennialInterstitial != null) && millennialInterstitial.isReady());
+ }
+
+
+ @Override
+ protected void showVideo() {
+
+ if ((millennialInterstitial != null) && millennialInterstitial.isReady()) {
+ try {
+ millennialInterstitial.show(context);
+ } catch (MMException e) {
+ Log.e(TAG, "An exception occurred showing the MM SDK interstitial.", e);
+ MoPubRewardedVideoManager
+ .onRewardedVideoPlaybackError(MillennialRewardedVideo.class, millennialInterstitial.placementId,
+ MoPubErrorCode.INTERNAL_ERROR);
+ }
+ } else {
+ Log.w(TAG, "showVideo called before MillennialInterstitial ad was loaded.");
+ }
+ }
+
+
+ class MillennialRewardedVideoListener
+ implements InterstitialListener, XIncentivizedEventListener, CustomEventRewardedVideoListener {
+
+ @Override
+ public void onAdLeftApplication(InterstitialAd interstitialAd) {
+ // onLeaveApplication is an alias to on clicked. We are not required to call this.
+
+ // @formatter:off
+ // https://github.com/mopub/mopub-android-sdk/blob/940eee70fe1980b4869d61cb5d668ccbab75c0ee/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/CustomEventInterstitial.java
+ // @formatter:on
+ Log.d(TAG, "Millennial Rewarded Video Ad - Leaving application");
+ }
+
+
+ @Override
+ public void onClicked(final InterstitialAd interstitialAd) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Ad was clicked");
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoClicked(MillennialRewardedVideo.class, interstitialAd.placementId);
+ }
+ });
+ }
+
+
+ @Override
+ public void onClosed(final InterstitialAd interstitialAd) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Ad was closed");
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoClosed(MillennialRewardedVideo.class, interstitialAd.placementId);
+ }
+ });
+ }
+
+
+ @Override
+ public void onExpired(final InterstitialAd interstitialAd) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Ad expired");
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoLoadFailure(MillennialRewardedVideo.class, interstitialAd.placementId,
+ MoPubErrorCode.VIDEO_NOT_AVAILABLE);
+ }
+ });
+ }
+
+
+ @Override
+ public void onLoadFailed(final InterstitialAd interstitialAd, InterstitialErrorStatus
+ interstitialErrorStatus) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - load failed (" + interstitialErrorStatus.getErrorCode() + "): " +
+ interstitialErrorStatus.getDescription());
+
+ final MoPubErrorCode moPubErrorCode;
+
+ switch (interstitialErrorStatus.getErrorCode()) {
+ case InterstitialErrorStatus.ALREADY_LOADED:
+ // This will generate discrepancies, as requests will NOT be sent to Millennial.
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoLoadSuccess(MillennialRewardedVideo.class, interstitialAd.placementId);
+ }
+ });
+ Log.w(TAG, "Millennial Rewarded Video Ad - Attempted to load ads when ads are already loaded.");
+ return;
+ case InterstitialErrorStatus.EXPIRED:
+ case InterstitialErrorStatus.DISPLAY_FAILED:
+ case InterstitialErrorStatus.INIT_FAILED:
+ case InterstitialErrorStatus.ADAPTER_NOT_FOUND:
+ moPubErrorCode = MoPubErrorCode.INTERNAL_ERROR;
+ break;
+ case InterstitialErrorStatus.NO_NETWORK:
+ moPubErrorCode = MoPubErrorCode.NO_CONNECTION;
+ break;
+ case InterstitialErrorStatus.UNKNOWN:
+ moPubErrorCode = MoPubErrorCode.UNSPECIFIED;
+ break;
+ case InterstitialErrorStatus.NOT_LOADED:
+ case InterstitialErrorStatus.LOAD_FAILED:
+ default:
+ moPubErrorCode = MoPubErrorCode.NETWORK_NO_FILL;
+ }
+
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoLoadFailure(MillennialRewardedVideo.class, interstitialAd.placementId,
+ moPubErrorCode);
+ }
+ });
+ }
+
+
+ @Override
+ public void onLoaded(final InterstitialAd interstitialAd) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Ad loaded splendidly");
+
+ CreativeInfo creativeInfo = getCreativeInfo();
+
+ if ((creativeInfo != null) && MMLog.isDebugEnabled()) {
+ MMLog.d(TAG, "Rewarded Video Creative Info: " + creativeInfo);
+ }
+
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoLoadSuccess(MillennialRewardedVideo.class, interstitialAd.placementId);
+ }
+ });
+ }
+
+
+ @Override
+ public void onShowFailed(final InterstitialAd interstitialAd, InterstitialErrorStatus
+ interstitialErrorStatus) {
+
+ Log.e(TAG, "Millennial Rewarded Video Ad - Show failed (" + interstitialErrorStatus.getErrorCode() + "): " +
+ interstitialErrorStatus.getDescription());
+
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoPlaybackError(MillennialRewardedVideo.class, interstitialAd.placementId,
+ MoPubErrorCode.VIDEO_PLAYBACK_ERROR);
+ }
+ });
+ }
+
+
+ @Override
+ public void onShown(final InterstitialAd interstitialAd) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Ad shown");
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoStarted(MillennialRewardedVideo.class, interstitialAd.placementId);
+ }
+ });
+ }
+
+
+ @Override
+ public boolean onVideoComplete() {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Video completed");
+ MillennialUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ MoPubRewardedVideoManager
+ .onRewardedVideoCompleted(MillennialRewardedVideo.class, millennialInterstitial.placementId,
+ MoPubReward.success(MoPubReward.NO_REWARD_LABEL, MoPubReward.DEFAULT_REWARD_AMOUNT));
+ }
+ });
+ return false;
+ }
+
+
+ @Override
+ public boolean onCustomEvent(XIncentiveEvent xIncentiveEvent) {
+
+ Log.d(TAG, "Millennial Rewarded Video Ad - Custom event received: " + xIncentiveEvent.eventId + ", " +
+ xIncentiveEvent.args);
+
+ return false;
+ }
+ }
+}
diff --git a/mopub-sample/AndroidManifest.xml b/mopub-sample/AndroidManifest.xml
index 963334ad1..814b58ea3 100644
--- a/mopub-sample/AndroidManifest.xml
+++ b/mopub-sample/AndroidManifest.xml
@@ -1,8 +1,8 @@
+ android:versionCode="57"
+ android:versionName="4.19.0">
@@ -17,11 +17,19 @@
android:networkSecurityConfig="@xml/network_security_config">
+
+
+
+
+
+
diff --git a/mopub-sample/build.gradle b/mopub-sample/build.gradle
index b9101ad42..a54d0f60c 100644
--- a/mopub-sample/build.gradle
+++ b/mopub-sample/build.gradle
@@ -11,7 +11,7 @@ apply plugin: 'com.android.application'
project.group = 'com.mopub'
project.description = '''MoPub Sample App'''
-project.version = '4.18.0'
+project.version = '4.19.0'
android {
compileSdkVersion 26
@@ -19,7 +19,7 @@ android {
lintOptions { abortOnError false }
defaultConfig {
- versionCode 56
+ versionCode 57
versionName version
minSdkVersion 16
targetSdkVersion 26
@@ -58,7 +58,8 @@ android {
dependencies {
compile 'com.android.support:support-v4:26.1.0'
- compile 'com.google.android.gms:play-services-ads:9.4.0'
+ compile 'com.google.android.gms:play-services-ads:11.4.0'
+ compile 'com.google.android.gms:play-services-base:11.4.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile project(':mopub-sdk')
}
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AbstractBannerDetailFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AbstractBannerDetailFragment.java
index f4ee63c90..c92575b3a 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AbstractBannerDetailFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AbstractBannerDetailFragment.java
@@ -44,6 +44,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
layoutParams.height = getHeight();
mMoPubView.setLayoutParams(layoutParams);
+ views.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
hideSoftKeyboard(views.mKeywordsField);
final String adUnitId = mMoPubSampleAdUnit.getAdUnitId();
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AdUnitDataSource.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AdUnitDataSource.java
index dbe77011d..3a1b987b9 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AdUnitDataSource.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/AdUnitDataSource.java
@@ -4,7 +4,9 @@
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.NonNull;
+import com.mopub.common.Preconditions;
import com.mopub.common.logging.MoPubLog;
import java.util.ArrayList;
@@ -53,7 +55,10 @@ MoPubSampleAdUnit createSampleAdUnit(final MoPubSampleAdUnit sampleAdUnit) {
}
private MoPubSampleAdUnit createSampleAdUnit(final MoPubSampleAdUnit sampleAdUnit,
- final boolean isUserGenerated) {
+ final boolean isUserGenerated) {
+ deleteAllAdUnitsWithAdUnitIdAndAdType(sampleAdUnit.getAdUnitId(),
+ sampleAdUnit.getFragmentClassName());
+
final ContentValues values = new ContentValues();
final int userGenerated = isUserGenerated ? 1 : 0;
values.put(COLUMN_AD_UNIT_ID, sampleAdUnit.getAdUnitId());
@@ -85,6 +90,20 @@ void deleteSampleAdUnit(final MoPubSampleAdUnit adConfiguration) {
database.close();
}
+ private void deleteAllAdUnitsWithAdUnitIdAndAdType(@NonNull final String adUnitId,
+ @NonNull final String adType) {
+ Preconditions.checkNotNull(adUnitId);
+ Preconditions.checkNotNull(adType);
+
+ final SQLiteDatabase database = mDatabaseHelper.getWritableDatabase();
+ final int numDeletedRows = database.delete(TABLE_AD_CONFIGURATIONS,
+ COLUMN_AD_UNIT_ID + " = '" + adUnitId +
+ "' AND " + COLUMN_USER_GENERATED + " = 1 AND " +
+ COLUMN_AD_TYPE + " = '" + adType + "'", null);
+ MoPubLog.d(numDeletedRows + " rows deleted with adUnitId: " + adUnitId);
+ database.close();
+ }
+
List getAllAdUnits() {
final List adConfigurations = new ArrayList<>();
SQLiteDatabase database = mDatabaseHelper.getReadableDatabase();
@@ -94,7 +113,9 @@ List getAllAdUnits() {
while (!cursor.isAfterLast()) {
final MoPubSampleAdUnit adConfiguration = cursorToAdConfiguration(cursor);
- adConfigurations.add(adConfiguration);
+ if (adConfiguration != null) {
+ adConfigurations.add(adConfiguration);
+ }
cursor.moveToNext();
}
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/InterstitialDetailFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/InterstitialDetailFragment.java
index f27ab6bb7..fec98dd95 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/InterstitialDetailFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/InterstitialDetailFragment.java
@@ -25,6 +25,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
MoPubSampleAdUnit.fromBundle(getArguments());
final View view = inflater.inflate(R.layout.interstitial_detail_fragment, container, false);
final DetailFragmentViewHolder views = DetailFragmentViewHolder.fromView(view);
+ views.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
hideSoftKeyboard(views.mKeywordsField);
final String adUnitId = adConfiguration.getAdUnitId();
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubListFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubListFragment.java
index d655656c2..ce6d1e79c 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubListFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubListFragment.java
@@ -3,11 +3,15 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
+import android.net.Uri;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -20,12 +24,14 @@
import android.widget.Toast;
import com.mopub.common.MoPub;
+import com.mopub.common.Preconditions;
import com.mopub.common.logging.MoPubLog;
import java.util.ArrayList;
import java.util.List;
import static com.mopub.simpleadsdemo.MoPubSampleAdUnit.AdType;
+import static com.mopub.simpleadsdemo.Utils.logToast;
interface TrashCanClickListener {
@@ -33,6 +39,12 @@ interface TrashCanClickListener {
}
public class MoPubListFragment extends ListFragment implements TrashCanClickListener {
+
+ private static final String AD_UNIT_ID_KEY = "adUnitId";
+ private static final String FORMAT_KEY = "format";
+ static final String KEYWORDS_KEY = "keywords";
+ private static final String NAME_KEY = "name";
+
private MoPubSampleListAdapter mAdapter;
private AdUnitDataSource mAdUnitDataSource;
@@ -44,6 +56,33 @@ public void onCreate(Bundle savedInstanceState) {
initializeAdapter();
}
+ void addAdUnitViaDeeplink(@Nullable final Uri deeplinkData) {
+ if (deeplinkData == null) {
+ return;
+ }
+
+ final String adUnitId = deeplinkData.getQueryParameter(AD_UNIT_ID_KEY);
+ try {
+ Utils.validateAdUnitId(adUnitId);
+ } catch (IllegalArgumentException e) {
+ logToast(getContext(), "Ignoring invalid ad unit: " + adUnitId);
+ return;
+ }
+
+ final String format = deeplinkData.getQueryParameter(FORMAT_KEY);
+ final AdType adType = AdType.fromDeeplinkString(format);
+ if (adType == null) {
+ logToast(getContext(), "Ignoring invalid ad format: " + format);
+ return;
+ }
+
+ final String name = deeplinkData.getQueryParameter(NAME_KEY);
+ final MoPubSampleAdUnit adUnit = new MoPubSampleAdUnit.Builder(adUnitId,
+ adType).description(name == null ? "" : name).build();
+ final MoPubSampleAdUnit newAdUnit = addAdUnit(adUnit);
+ enterAdFragment(newAdUnit, deeplinkData.getQueryParameter(KEYWORDS_KEY));
+ }
+
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.ad_unit_list_fragment, container, false);
@@ -67,6 +106,15 @@ public void onListItemClick(ListView listView, View view, int position, long id)
final MoPubSampleAdUnit adConfiguration = mAdapter.getItem(position);
+ if (adConfiguration != null) {
+ enterAdFragment(adConfiguration, null);
+ }
+ }
+
+ private void enterAdFragment(@NonNull final MoPubSampleAdUnit adConfiguration,
+ @Nullable final String keywords) {
+ Preconditions.checkNotNull(adConfiguration);
+
final FragmentTransaction fragmentTransaction =
getActivity().getSupportFragmentManager().beginTransaction();
@@ -83,7 +131,15 @@ public void onListItemClick(ListView listView, View view, int position, long id)
return;
}
- fragment.setArguments(adConfiguration.toBundle());
+ final Bundle bundle = adConfiguration.toBundle();
+ if (!TextUtils.isEmpty(keywords)) {
+ bundle.putString(KEYWORDS_KEY, keywords);
+ }
+ fragment.setArguments(bundle);
+
+ if (getFragmentManager().getBackStackEntryCount() > 0) {
+ getFragmentManager().popBackStack();
+ }
fragmentTransaction
.replace(R.id.fragment_container, fragment)
@@ -130,10 +186,28 @@ public void onPause() {
super.onPause();
}
- void addAdUnit(final MoPubSampleAdUnit moPubSampleAdUnit) {
- MoPubSampleAdUnit createdAdUnit = mAdUnitDataSource.createSampleAdUnit(moPubSampleAdUnit);
+ @NonNull
+ MoPubSampleAdUnit addAdUnit(@NonNull final MoPubSampleAdUnit moPubSampleAdUnit) {
+ Preconditions.checkNotNull(moPubSampleAdUnit);
+
+ final MoPubSampleAdUnit createdAdUnit =
+ mAdUnitDataSource.createSampleAdUnit(moPubSampleAdUnit);
+
+ for (int i = 0; i < mAdapter.getCount(); i++) {
+ final MoPubSampleAdUnit currentAdUnit = mAdapter.getItem(i);
+ if (currentAdUnit != null &&
+ moPubSampleAdUnit.getAdUnitId().equals(currentAdUnit.getAdUnitId()) &&
+ moPubSampleAdUnit.getFragmentClassName().equals(
+ currentAdUnit.getFragmentClassName()) &&
+ currentAdUnit.isUserDefined()) {
+ mAdapter.remove(currentAdUnit);
+ logToast(getContext(), moPubSampleAdUnit.getAdUnitId() + " replaced.");
+ break;
+ }
+ }
mAdapter.add(createdAdUnit);
mAdapter.sort(MoPubSampleAdUnit.COMPARATOR);
+ return createdAdUnit;
}
void deleteAdUnit(final MoPubSampleAdUnit moPubSampleAdUnit) {
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleActivity.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleActivity.java
index fc175ea0b..a40e9dfba 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleActivity.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleActivity.java
@@ -1,11 +1,12 @@
package com.mopub.simpleadsdemo;
import android.annotation.TargetApi;
+import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.FragmentManager;
import android.webkit.WebView;
import com.mopub.common.MoPub;
@@ -37,6 +38,9 @@ private static void setWebDebugging() {
}
}
+ private MoPubListFragment mMoPubListFragment;
+ private Intent mDeeplinkIntent;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,17 +67,35 @@ protected void onCreate(Bundle savedInstanceState) {
MoPub.setLocationAwareness(MoPub.LocationAwareness.TRUNCATED);
MoPub.setLocationPrecision(4);
- if (findViewById(R.id.fragment_container) != null) {
- final MoPubListFragment listFragment = new MoPubListFragment();
- listFragment.setArguments(getIntent().getExtras());
- FragmentManager fragmentManager = getSupportFragmentManager();
- fragmentManager.beginTransaction()
- .add(R.id.fragment_container, listFragment)
- .commit();
- }
+ createMoPubListFragment(getIntent());
// Intercepts all logs including Level.FINEST so we can show a toast
// that is not normally user-facing. This is only used for native ads.
LoggingUtils.enableCanaryLogging(this);
}
+
+ private void createMoPubListFragment(@NonNull final Intent intent) {
+ if (findViewById(R.id.fragment_container) != null) {
+ mMoPubListFragment = new MoPubListFragment();
+ mMoPubListFragment.setArguments(intent.getExtras());
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.fragment_container, mMoPubListFragment).commit();
+
+ mDeeplinkIntent = intent;
+ }
+ }
+
+ @Override
+ public void onNewIntent(@NonNull final Intent intent) {
+ mDeeplinkIntent = intent;
+ }
+
+ @Override
+ public void onPostResume() {
+ super.onPostResume();
+ if (mMoPubListFragment != null && mDeeplinkIntent != null) {
+ mMoPubListFragment.addAdUnitViaDeeplink(mDeeplinkIntent.getData());
+ mDeeplinkIntent = null;
+ }
+ }
}
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleAdUnit.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleAdUnit.java
index f6573175d..ea3bb0d3c 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleAdUnit.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/MoPubSampleAdUnit.java
@@ -2,9 +2,11 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import java.util.Comparator;
+import java.util.Locale;
class MoPubSampleAdUnit implements Comparable {
@@ -51,6 +53,35 @@ static AdType fromFragmentClassName(final String fragmentClassName) {
return null;
}
+
+ @Nullable
+ static AdType fromDeeplinkString(@Nullable final String adType) {
+ if (adType == null) {
+ return null;
+ }
+ switch (adType.toLowerCase(Locale.US)) {
+ case "banner":
+ return BANNER;
+ case "interstitial":
+ return INTERSTITIAL;
+ case "mrect":
+ return MRECT;
+ case "leaderboard":
+ return LEADERBOARD;
+ case "skyscraper":
+ return SKYSCRAPER;
+ case "rewarded":
+ return REWARDED_VIDEO;
+ case "native":
+ return LIST_VIEW;
+ case "nativetableplacer":
+ return RECYCLER_VIEW;
+ case "nativecollectionplacer":
+ return CUSTOM_NATIVE;
+ default:
+ return null;
+ }
+ }
}
static final Comparator COMPARATOR =
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeGalleryFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeGalleryFragment.java
index b7141689d..b62f18c56 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeGalleryFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeGalleryFragment.java
@@ -69,6 +69,7 @@ public void onClick(View view) {
final String adUnitId = mAdConfiguration.getAdUnitId();
views.mDescriptionView.setText(mAdConfiguration.getDescription());
views.mAdUnitIdView.setText(adUnitId);
+ views.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
mViewPager = (ViewPager) view.findViewById(R.id.gallery_pager);
// Set up a renderer for a static native ad.
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeListViewFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeListViewFragment.java
index b0e6cc5ed..6616d4e3c 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeListViewFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeListViewFragment.java
@@ -64,6 +64,7 @@ public void onClick(View view) {
final String adUnitId = mAdConfiguration.getAdUnitId();
views.mDescriptionView.setText(mAdConfiguration.getDescription());
views.mAdUnitIdView.setText(adUnitId);
+ views.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
final ArrayAdapter adapter = new ArrayAdapter(getActivity(),
android.R.layout.simple_list_item_1);
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeRecyclerViewFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeRecyclerViewFragment.java
index 472a5648e..f9b9d0951 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeRecyclerViewFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/NativeRecyclerViewFragment.java
@@ -80,6 +80,7 @@ public void onClick(final View v) {
final String adUnitId = mAdConfiguration.getAdUnitId();
viewHolder.mDescriptionView.setText(mAdConfiguration.getDescription());
viewHolder.mAdUnitIdView.setText(adUnitId);
+ viewHolder.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
final RecyclerView.Adapter originalAdapter = new DemoRecyclerAdapter();
diff --git a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/RewardedVideoDetailFragment.java b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/RewardedVideoDetailFragment.java
index 09dd38189..8f66f3d2e 100644
--- a/mopub-sample/src/main/java/com/mopub/simpleadsdemo/RewardedVideoDetailFragment.java
+++ b/mopub-sample/src/main/java/com/mopub/simpleadsdemo/RewardedVideoDetailFragment.java
@@ -50,6 +50,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
MoPubSampleAdUnit.fromBundle(getArguments());
final View view = inflater.inflate(R.layout.interstitial_detail_fragment, container, false);
final DetailFragmentViewHolder views = DetailFragmentViewHolder.fromView(view);
+ views.mKeywordsField.setText(getArguments().getString(MoPubListFragment.KEYWORDS_KEY, ""));
hideSoftKeyboard(views.mKeywordsField);
if (!sRewardedVideoInitialized) {
diff --git a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/BannerVisibilityTracker.java b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/BannerVisibilityTracker.java
new file mode 100644
index 000000000..7649a5068
--- /dev/null
+++ b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/BannerVisibilityTracker.java
@@ -0,0 +1,294 @@
+package com.mopub.mobileads;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.mopub.common.Preconditions;
+import com.mopub.common.VisibleForTesting;
+import com.mopub.common.logging.MoPubLog;
+import com.mopub.common.util.Dips;
+import com.mopub.common.util.Views;
+
+import java.lang.ref.WeakReference;
+
+import static android.view.ViewTreeObserver.OnPreDrawListener;
+
+/**
+ * Tracks banner views to determine when they become visible, where visibility is determined by
+ * whether a minimum number of dips have been visible for a minimum duration, where both values are
+ * configured by the AdServer via headers.
+ */
+class BannerVisibilityTracker {
+ // Time interval to use for throttling visibility checks.
+ private static final int VISIBILITY_THROTTLE_MILLIS = 100;
+
+ /**
+ * Callback when visibility conditions are satisfied.
+ */
+ interface BannerVisibilityTrackerListener {
+ void onVisibilityChanged();
+ }
+
+ @NonNull @VisibleForTesting final OnPreDrawListener mOnPreDrawListener;
+ @NonNull @VisibleForTesting WeakReference mWeakViewTreeObserver;
+
+ /**
+ * Banner view that is being tracked.
+ */
+ @NonNull private final View mTrackedView;
+
+ /**
+ * Root view of banner view being tracked.
+ */
+ @NonNull private final View mRootView;
+
+ /**
+ * Object to check actual visibility.
+ */
+ @NonNull private final BannerVisibilityChecker mVisibilityChecker;
+
+ /**
+ * Callback listener.
+ */
+ @Nullable private BannerVisibilityTrackerListener mBannerVisibilityTrackerListener;
+
+ /**
+ * Runnable to run on each visibility loop.
+ */
+ @NonNull private final BannerVisibilityRunnable mVisibilityRunnable;
+
+ /**
+ * Handler for visibility.
+ */
+ @NonNull private final Handler mVisibilityHandler;
+
+ /**
+ * Whether the visibility runnable is scheduled.
+ */
+ private boolean mIsVisibilityScheduled;
+
+ /**
+ * Whether the imp tracker has been fired already.
+ */
+ private boolean mIsImpTrackerFired;
+
+ @VisibleForTesting
+ public BannerVisibilityTracker(@NonNull final Context context,
+ @NonNull final View rootView,
+ @NonNull final View trackedView,
+ final int minVisibleDips,
+ final int minVisibleMillis) {
+ Preconditions.checkNotNull(rootView);
+ Preconditions.checkNotNull(trackedView);
+
+ mRootView = rootView;
+ mTrackedView = trackedView;
+
+ mVisibilityChecker = new BannerVisibilityChecker(minVisibleDips, minVisibleMillis);
+ mVisibilityHandler = new Handler();
+ mVisibilityRunnable = new BannerVisibilityRunnable();
+
+ mOnPreDrawListener = new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ scheduleVisibilityCheck();
+ return true;
+ }
+ };
+
+ mWeakViewTreeObserver = new WeakReference(null);
+ setViewTreeObserver(context, mTrackedView);
+ }
+
+ private void setViewTreeObserver(@Nullable final Context context, @Nullable final View view) {
+ final ViewTreeObserver originalViewTreeObserver = mWeakViewTreeObserver.get();
+ if (originalViewTreeObserver != null && originalViewTreeObserver.isAlive()) {
+ return;
+ }
+
+ final View rootView = Views.getTopmostView(context, view);
+ if (rootView == null) {
+ MoPubLog.d("Unable to set Visibility Tracker due to no available root view.");
+ return;
+ }
+
+ final ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
+ if (!viewTreeObserver.isAlive()) {
+ MoPubLog.w("Visibility Tracker was unable to track views because the"
+ + " root view tree observer was not alive");
+ return;
+ }
+
+ mWeakViewTreeObserver = new WeakReference<>(viewTreeObserver);
+ viewTreeObserver.addOnPreDrawListener(mOnPreDrawListener);
+ }
+
+ @Nullable
+ @Deprecated
+ @VisibleForTesting
+ BannerVisibilityTrackerListener getBannerVisibilityTrackerListener() {
+ return mBannerVisibilityTrackerListener;
+ }
+
+ void setBannerVisibilityTrackerListener(
+ @Nullable final BannerVisibilityTrackerListener bannerVisibilityTrackerListener) {
+ mBannerVisibilityTrackerListener = bannerVisibilityTrackerListener;
+ }
+
+ /**
+ * Destroy the visibility tracker, preventing it from future use.
+ */
+ void destroy() {
+ mVisibilityHandler.removeMessages(0);
+ mIsVisibilityScheduled = false;
+ final ViewTreeObserver viewTreeObserver = mWeakViewTreeObserver.get();
+ if (viewTreeObserver != null && viewTreeObserver.isAlive()) {
+ viewTreeObserver.removeOnPreDrawListener(mOnPreDrawListener);
+ }
+ mWeakViewTreeObserver.clear();
+ mBannerVisibilityTrackerListener = null;
+ }
+
+ void scheduleVisibilityCheck() {
+ // Tracking this directly instead of calling hasMessages directly because we measured that
+ // this led to slightly better performance.
+ if (mIsVisibilityScheduled) {
+ return;
+ }
+
+ mIsVisibilityScheduled = true;
+ mVisibilityHandler.postDelayed(mVisibilityRunnable, VISIBILITY_THROTTLE_MILLIS);
+ }
+
+ @NonNull
+ @Deprecated
+ @VisibleForTesting
+ BannerVisibilityChecker getBannerVisibilityChecker() {
+ return mVisibilityChecker;
+ }
+
+ @NonNull
+ @Deprecated
+ @VisibleForTesting
+ Handler getVisibilityHandler() {
+ return mVisibilityHandler;
+ }
+
+ @Deprecated
+ @VisibleForTesting
+ boolean isVisibilityScheduled() {
+ return mIsVisibilityScheduled;
+ }
+
+ @Deprecated
+ @VisibleForTesting
+ boolean isImpTrackerFired() {
+ return mIsImpTrackerFired;
+ }
+
+ class BannerVisibilityRunnable implements Runnable {
+ @Override
+ public void run() {
+ if (mIsImpTrackerFired) {
+ return;
+ }
+
+ mIsVisibilityScheduled = false;
+
+ // If the view meets the dips count requirement for visibility, then also check the
+ // duration requirement for visibility.
+ if (mVisibilityChecker.isVisible(mRootView, mTrackedView)) {
+ // Start the timer for duration requirement if it hasn't already.
+ if (!mVisibilityChecker.hasBeenVisibleYet()) {
+ mVisibilityChecker.setStartTimeMillis();
+ }
+
+ if (mVisibilityChecker.hasRequiredTimeElapsed()) {
+ if (mBannerVisibilityTrackerListener != null) {
+ mBannerVisibilityTrackerListener.onVisibilityChanged();
+ mIsImpTrackerFired = true;
+ }
+ }
+ }
+
+ // If visibility requirements are not met, check again later.
+ if (!mIsImpTrackerFired) {
+ scheduleVisibilityCheck();
+ }
+ }
+ }
+
+ static class BannerVisibilityChecker {
+ private int mMinVisibleDips;
+ private int mMinVisibleMillis;
+ private long mStartTimeMillis = Long.MIN_VALUE;
+
+ // A rect to use for hit testing. Create this once to avoid excess garbage collection
+ private final Rect mClipRect = new Rect();
+
+ BannerVisibilityChecker(final int minVisibleDips, final int minVisibleMillis) {
+ mMinVisibleDips = minVisibleDips;
+ mMinVisibleMillis = minVisibleMillis;
+ }
+
+ boolean hasBeenVisibleYet() {
+ return mStartTimeMillis != Long.MIN_VALUE;
+ }
+
+ void setStartTimeMillis() {
+ mStartTimeMillis = SystemClock.uptimeMillis();
+ }
+
+ /**
+ * Whether the visible time has elapsed from the start time.
+ */
+ boolean hasRequiredTimeElapsed() {
+ if (!hasBeenVisibleYet()) {
+ return false;
+ }
+
+ return SystemClock.uptimeMillis() - mStartTimeMillis >= mMinVisibleMillis;
+ }
+
+ /**
+ * Whether the visible dips count requirement is met.
+ */
+ boolean isVisible(@Nullable final View rootView, @Nullable final View view) {
+ // ListView & GridView both call detachFromParent() for views that can be recycled for
+ // new data. This is one of the rare instances where a view will have a null parent for
+ // an extended period of time and will not be the main window.
+ // view.getGlobalVisibleRect() doesn't check that case, so if the view has visibility
+ // of View.VISIBLE but its group has no parent it is likely in the recycle bin of a
+ // ListView / GridView and not on screen.
+ if (view == null || view.getVisibility() != View.VISIBLE || rootView.getParent() == null) {
+ return false;
+ }
+
+ // If either width or height is non-positive, the view cannot be visible.
+ if (view.getWidth() <= 0 || view.getHeight() <= 0) {
+ return false;
+ }
+
+ // View completely clipped by its parents
+ if (!view.getGlobalVisibleRect(mClipRect)) {
+ return false;
+ }
+
+ // Calculate area of view not clipped by any of its parents
+ final int widthInDips = Dips.pixelsToIntDips((float) mClipRect.width(),
+ view.getContext());
+ final int heightInDips = Dips.pixelsToIntDips((float) mClipRect.height(),
+ view.getContext());
+ final long visibleViewAreaInDips = (long) (widthInDips * heightInDips);
+
+ return visibleViewAreaInDips >= mMinVisibleDips;
+ }
+ }
+}
+
diff --git a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBanner.java b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBanner.java
index 0db1f4088..be3c00627 100644
--- a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBanner.java
+++ b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBanner.java
@@ -37,7 +37,13 @@ protected abstract void loadBanner(Context context,
* Called when a Custom Event is being invalidated or destroyed. Perform any final cleanup here.
*/
protected abstract void onInvalidate();
-
+
+ /*
+ * Fire MPX impression trackers and 3rd-party impression trackers from JS.
+ */
+ protected void trackMpxAndThirdPartyImpressions() {
+ }
+
public interface CustomEventBannerListener {
/*
* Your custom event subclass must call this method when it successfully loads an ad and
diff --git a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBannerAdapter.java b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBannerAdapter.java
index ef72d1bae..9ae515fdc 100644
--- a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBannerAdapter.java
+++ b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/CustomEventBannerAdapter.java
@@ -4,11 +4,14 @@
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import android.view.View;
import com.mopub.common.AdReport;
import com.mopub.common.Constants;
+import com.mopub.common.DataKeys;
import com.mopub.common.Preconditions;
+import com.mopub.common.VisibleForTesting;
import com.mopub.common.logging.MoPubLog;
import com.mopub.common.util.ReflectionTarget;
import com.mopub.mobileads.CustomEventBanner.CustomEventBannerListener;
@@ -38,6 +41,11 @@ public class CustomEventBannerAdapter implements CustomEventBannerListener {
private final Runnable mTimeout;
private boolean mStoredAutorefresh;
+ private int mImpressionMinVisibleDips = Integer.MIN_VALUE;
+ private int mImpressionMinVisibleMs = Integer.MIN_VALUE;
+ private boolean mIsVisibilityImpressionTrackingEnabled = false;
+ @Nullable private BannerVisibilityTracker mVisibilityTracker;
+
public CustomEventBannerAdapter(@NonNull MoPubView moPubView,
@NonNull String className,
@NonNull Map serverExtras,
@@ -68,6 +76,9 @@ public void run() {
// Attempt to load the JSON extras into mServerExtras.
mServerExtras = new TreeMap(serverExtras);
+ // Parse banner impression tracking headers to determine if we are in visibility experiment
+ parseBannerImpressionTrackingHeaders();
+
mLocalExtras = mMoPubView.getLocalExtras();
if (mMoPubView.getLocation() != null) {
mLocalExtras.put("location", mMoPubView.getLocation());
@@ -107,6 +118,13 @@ void invalidate() {
MoPubLog.d("Invalidating a custom event banner threw an exception", e);
}
}
+ if (mVisibilityTracker != null) {
+ try {
+ mVisibilityTracker.destroy();
+ } catch (Exception e) {
+ MoPubLog.d("Destroying a banner visibility tracker threw an exception", e);
+ }
+ }
mContext = null;
mCustomEventBanner = null;
mLocalExtras = null;
@@ -118,6 +136,31 @@ boolean isInvalidated() {
return mInvalidated;
}
+ @Deprecated
+ @VisibleForTesting
+ int getImpressionMinVisibleDips() {
+ return mImpressionMinVisibleDips;
+ }
+
+ @Deprecated
+ @VisibleForTesting
+ int getImpressionMinVisibleMs() {
+ return mImpressionMinVisibleMs;
+ }
+
+ @Deprecated
+ @VisibleForTesting
+ boolean isVisibilityImpressionTrackingEnabled() {
+ return mIsVisibilityImpressionTrackingEnabled;
+ }
+
+ @Nullable
+ @Deprecated
+ @VisibleForTesting
+ BannerVisibilityTracker getVisibilityTracker() {
+ return mVisibilityTracker;
+ }
+
private void cancelTimeout() {
mHandler.removeCallbacks(mTimeout);
}
@@ -132,6 +175,34 @@ private int getTimeoutDelayMilliseconds() {
return mMoPubView.getAdTimeoutDelay() * 1000;
}
+ private void parseBannerImpressionTrackingHeaders() {
+ final String impressionMinVisibleDipsString =
+ mServerExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS);
+ final String impressionMinVisibleMsString =
+ mServerExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS);
+
+ if (!TextUtils.isEmpty(impressionMinVisibleDipsString)
+ && !TextUtils.isEmpty(impressionMinVisibleMsString)) {
+ try {
+ mImpressionMinVisibleDips = Integer.parseInt(impressionMinVisibleDipsString);
+ } catch (NumberFormatException e) {
+ MoPubLog.d("Cannot parse integer from header "
+ + DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS);
+ }
+
+ try {
+ mImpressionMinVisibleMs = Integer.parseInt(impressionMinVisibleMsString);
+ } catch (NumberFormatException e) {
+ MoPubLog.d("Cannot parse integer from header "
+ + DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS);
+ }
+
+ if (mImpressionMinVisibleDips > 0 && mImpressionMinVisibleMs >= 0) {
+ mIsVisibilityImpressionTrackingEnabled = true;
+ }
+ }
+ }
+
/*
* CustomEventBanner.Listener implementation
*/
@@ -145,9 +216,36 @@ public void onBannerLoaded(View bannerView) {
if (mMoPubView != null) {
mMoPubView.nativeAdLoaded();
+
+ // If visibility impression tracking is enabled for banners, fire all impression
+ // tracking URLs (AdServer, MPX, 3rd-party) for both HTML and MRAID banner types when
+ // visibility conditions are met.
+ //
+ // Else, retain old behavior of firing AdServer impression tracking URL if and only if
+ // banner is not HTML.
+ if (mIsVisibilityImpressionTrackingEnabled) {
+ // Set up visibility tracker and listener if in experiment
+ mVisibilityTracker = new BannerVisibilityTracker(mContext, mMoPubView, bannerView,
+ mImpressionMinVisibleDips, mImpressionMinVisibleMs);
+ mVisibilityTracker.setBannerVisibilityTrackerListener(
+ new BannerVisibilityTracker.BannerVisibilityTrackerListener() {
+ @Override
+ public void onVisibilityChanged() {
+ mMoPubView.trackNativeImpression();
+ if (mCustomEventBanner != null) {
+ mCustomEventBanner.trackMpxAndThirdPartyImpressions();
+ }
+ }
+ });
+ }
+
mMoPubView.setAdContentView(bannerView);
- if (!(bannerView instanceof HtmlBannerWebView)) {
- mMoPubView.trackNativeImpression();
+
+ // Old behavior
+ if (!mIsVisibilityImpressionTrackingEnabled) {
+ if (!(bannerView instanceof HtmlBannerWebView)) {
+ mMoPubView.trackNativeImpression();
+ }
}
}
}
diff --git a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/HtmlBanner.java b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/HtmlBanner.java
index 6104ff90a..1cf274fb5 100644
--- a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/HtmlBanner.java
+++ b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mobileads/HtmlBanner.java
@@ -13,6 +13,7 @@
import java.util.Map;
import static com.mopub.common.DataKeys.AD_REPORT_KEY;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
import static com.mopub.mobileads.MoPubErrorCode.INTERNAL_ERROR;
import static com.mopub.mobileads.MoPubErrorCode.NETWORK_INVALID_STATE;
@@ -37,6 +38,7 @@ protected void loadBanner(
redirectUrl = serverExtras.get(DataKeys.REDIRECT_URL_KEY);
clickthroughUrl = serverExtras.get(DataKeys.CLICKTHROUGH_URL_KEY);
isScrollable = Boolean.valueOf(serverExtras.get(DataKeys.SCROLLABLE_KEY));
+
try {
adReport = (AdReport) localExtras.get(AD_REPORT_KEY);
} catch (ClassCastException e) {
@@ -75,6 +77,11 @@ protected void onInvalidate() {
}
}
+ @Override
+ protected void trackMpxAndThirdPartyImpressions() {
+ mHtmlBannerWebView.loadUrl(WEB_VIEW_DID_APPEAR.getUrl());
+ }
+
private boolean extrasAreValid(Map serverExtras) {
return serverExtras.containsKey(DataKeys.HTML_RESPONSE_BODY_KEY);
}
diff --git a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mraid/MraidBanner.java b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mraid/MraidBanner.java
index 86526d22b..5e13128f5 100644
--- a/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mraid/MraidBanner.java
+++ b/mopub-sdk/mopub-sdk-banner/src/main/java/com/mopub/mraid/MraidBanner.java
@@ -18,6 +18,7 @@
import static com.mopub.common.DataKeys.AD_REPORT_KEY;
import static com.mopub.common.DataKeys.HTML_RESPONSE_BODY_KEY;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
import static com.mopub.mobileads.MoPubErrorCode.MRAID_LOAD_ERROR;
class MraidBanner extends CustomEventBanner {
@@ -95,15 +96,19 @@ public void onReady(final @NonNull MraidBridge.MraidWebView webView,
@Override
protected void onInvalidate() {
+ if (mExternalViewabilitySessionManager != null) {
+ mExternalViewabilitySessionManager.endDisplaySession();
+ mExternalViewabilitySessionManager = null;
+ }
if (mMraidController != null) {
mMraidController.setMraidListener(null);
mMraidController.destroy();
}
+ }
- if (mExternalViewabilitySessionManager != null) {
- mExternalViewabilitySessionManager.endDisplaySession();
- mExternalViewabilitySessionManager = null;
- }
+ @Override
+ protected void trackMpxAndThirdPartyImpressions() {
+ mMraidController.loadJavascript(WEB_VIEW_DID_APPEAR.getJavascript());
}
private boolean extrasAreValid(@NonNull final Map serverExtras) {
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/BrowserWebViewClient.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/BrowserWebViewClient.java
index e7fc2d0cb..66de74134 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/BrowserWebViewClient.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/BrowserWebViewClient.java
@@ -8,7 +8,6 @@
import android.webkit.WebViewClient;
import com.mopub.common.logging.MoPubLog;
-import com.mopub.exceptions.IntentNotResolvableException;
import java.util.EnumSet;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/DataKeys.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/DataKeys.java
index be2193a93..1de41b771 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/DataKeys.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/DataKeys.java
@@ -17,9 +17,14 @@ public class DataKeys {
public static final String AD_WIDTH = "com_mopub_ad_width";
public static final String AD_HEIGHT = "com_mopub_ad_height";
+ // Banner imp tracking fields
+ public static final String BANNER_IMPRESSION_MIN_VISIBLE_DIPS = "Banner-Impression-Min-Pixels";
+ public static final String BANNER_IMPRESSION_MIN_VISIBLE_MS = "Banner-Impression-Min-Ms";
+
// Native fields
public static final String IMPRESSION_MIN_VISIBLE_PERCENT = "Impression-Min-Visible-Percent";
public static final String IMPRESSION_VISIBLE_MS = "Impression-Visible-Ms";
+ public static final String IMPRESSION_MIN_VISIBLE_PX = "Impression-Min-Visible-Px";
// Native Video fields
public static final String PLAY_VISIBLE_PERCENT = "Play-Visible-Percent";
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java
index c154a387a..e4b96b6e3 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPub.java
@@ -13,7 +13,7 @@
import static com.mopub.common.ExternalViewabilitySessionManager.ViewabilityVendor;
public class MoPub {
- public static final String SDK_VERSION = "4.18.0";
+ public static final String SDK_VERSION = "4.19.0";
public enum LocationAwareness { NORMAL, TRUNCATED, DISABLED }
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPubBrowser.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPubBrowser.java
index 59865d448..c60702bf8 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPubBrowser.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/MoPubBrowser.java
@@ -24,12 +24,12 @@
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-import static com.mopub.common.event.BaseEvent.*;
+import static com.mopub.common.event.BaseEvent.Category;
+import static com.mopub.common.event.BaseEvent.Name;
+import static com.mopub.common.event.BaseEvent.SamplingRate;
import static com.mopub.common.util.Drawables.BACKGROUND;
import static com.mopub.common.util.Drawables.CLOSE;
-import static com.mopub.common.util.Drawables.LEFT_ARROW;
import static com.mopub.common.util.Drawables.REFRESH;
-import static com.mopub.common.util.Drawables.RIGHT_ARROW;
import static com.mopub.common.util.Drawables.UNLEFT_ARROW;
import static com.mopub.common.util.Drawables.UNRIGHT_ARROW;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/logging/MoPubLog.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/logging/MoPubLog.java
index 0eac63140..fb527feff 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/logging/MoPubLog.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/logging/MoPubLog.java
@@ -1,6 +1,5 @@
package com.mopub.common.logging;
-import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.util.Log;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ImageUtils.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ImageUtils.java
index deb8928b6..15177acd6 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ImageUtils.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ImageUtils.java
@@ -1,10 +1,7 @@
package com.mopub.common.util;
import android.graphics.Bitmap;
-import android.os.Build;
import android.support.annotation.NonNull;
-import android.widget.ImageView;
-
public class ImageUtils {
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/JavaScriptWebViewCallbacks.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/JavaScriptWebViewCallbacks.java
new file mode 100644
index 000000000..143dc2e7c
--- /dev/null
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/JavaScriptWebViewCallbacks.java
@@ -0,0 +1,22 @@
+package com.mopub.common.util;
+
+public enum JavaScriptWebViewCallbacks {
+ // The ad server appends these functions to the MRAID javascript to help with third party
+ // impression tracking.
+ WEB_VIEW_DID_APPEAR("webviewDidAppear();"),
+ WEB_VIEW_DID_CLOSE("webviewDidClose();");
+
+ private String mJavascript;
+
+ JavaScriptWebViewCallbacks(String javascript) {
+ mJavascript = javascript;
+ }
+
+ public String getJavascript() {
+ return mJavascript;
+ }
+
+ public String getUrl() {
+ return "javascript:" + mJavascript;
+ }
+}
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Reflection.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Reflection.java
index e59d522a6..cf94ec5b7 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Reflection.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Reflection.java
@@ -4,7 +4,6 @@
import android.support.annotation.Nullable;
import com.mopub.common.Preconditions;
-import com.mopub.common.logging.MoPubLog;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ResponseHeader.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ResponseHeader.java
index 9c45e5154..ad909dbed 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ResponseHeader.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/ResponseHeader.java
@@ -27,9 +27,14 @@ public enum ResponseHeader {
ACCEPT_LANGUAGE("Accept-Language"),
BROWSER_AGENT("X-Browser-Agent"),
+ // Banner impression tracking fields
+ BANNER_IMPRESSION_MIN_VISIBLE_DIPS("X-Banner-Impression-Min-Pixels"),
+ BANNER_IMPRESSION_MIN_VISIBLE_MS("X-Banner-Impression-Min-Ms"),
+
// Native fields
IMPRESSION_MIN_VISIBLE_PERCENT("X-Impression-Min-Visible-Percent"),
IMPRESSION_VISIBLE_MS("X-Impression-Visible-Ms"),
+ IMPRESSION_MIN_VISIBLE_PX("X-Native-Impression-Min-Px"),
// Native Video fields
PLAY_VISIBLE_PERCENT("X-Play-Visible-Percent"),
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Streams.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Streams.java
index 0572a7cfb..fabe64695 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Streams.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/common/util/Streams.java
@@ -1,5 +1,7 @@
package com.mopub.common.util;
+import com.mopub.common.logging.MoPubLog;
+
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
@@ -60,8 +62,9 @@ public static void closeStream(Closeable stream) {
try {
stream.close();
- } catch (IOException e) {
+ } catch (Exception e) {
// Unable to close the stream
+ MoPubLog.d("Unable to close stream. Ignoring.");
}
}
}
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/BaseVideoViewController.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/BaseVideoViewController.java
index 6bc577f07..9e31439a0 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/BaseVideoViewController.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/BaseVideoViewController.java
@@ -13,12 +13,13 @@
import android.widget.VideoView;
import com.mopub.common.IntentActions;
+import com.mopub.common.Preconditions;
import com.mopub.common.logging.MoPubLog;
public abstract class BaseVideoViewController {
private final Context mContext;
private final RelativeLayout mLayout;
- private final BaseVideoViewControllerListener mBaseVideoViewControllerListener;
+ @NonNull private final BaseVideoViewControllerListener mBaseVideoViewControllerListener;
@Nullable private Long mBroadcastIdentifier;
public interface BaseVideoViewControllerListener {
@@ -30,7 +31,11 @@ void onStartActivityForResult(final Class extends Activity> clazz,
final Bundle extras);
}
- protected BaseVideoViewController(final Context context, @Nullable final Long broadcastIdentifier, final BaseVideoViewControllerListener baseVideoViewControllerListener) {
+ protected BaseVideoViewController(final Context context,
+ @Nullable final Long broadcastIdentifier,
+ @NonNull final BaseVideoViewControllerListener baseVideoViewControllerListener) {
+ Preconditions.checkNotNull(baseVideoViewControllerListener);
+
mContext = context;
mBroadcastIdentifier = broadcastIdentifier;
mBaseVideoViewControllerListener = baseVideoViewControllerListener;
@@ -61,6 +66,7 @@ void onActivityResult(final int requestCode, final int resultCode, final Intent
// By default, the activity result is ignored
}
+ @NonNull
protected BaseVideoViewControllerListener getBaseVideoViewControllerListener() {
return mBaseVideoViewControllerListener;
}
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/HtmlWebViewClient.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/HtmlWebViewClient.java
index eda4eea5a..743c4d691 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/HtmlWebViewClient.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/HtmlWebViewClient.java
@@ -4,11 +4,9 @@
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull;
-import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import com.mopub.common.Preconditions;
import com.mopub.common.UrlAction;
import com.mopub.common.UrlHandler;
import com.mopub.common.logging.MoPubLog;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/MoatBuyerTagXmlManager.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/MoatBuyerTagXmlManager.java
index 5472c9718..18a3a137c 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/MoatBuyerTagXmlManager.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/MoatBuyerTagXmlManager.java
@@ -6,7 +6,6 @@
import com.mopub.common.Preconditions;
import com.mopub.mobileads.util.XmlUtils;
-import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.HashSet;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTask.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTask.java
index dcbcbba99..dbf37d7f4 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTask.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTask.java
@@ -3,7 +3,6 @@
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.os.AsyncTask;
-import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ImageView;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoView.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoView.java
index d4f9949d3..a34b447bb 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoView.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastVideoView.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.media.MediaMetadataRetriever;
import android.os.AsyncTask;
-import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.ImageView;
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastWrapperXmlManager.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastWrapperXmlManager.java
index 0bf3c15ae..a70280797 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastWrapperXmlManager.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/VastWrapperXmlManager.java
@@ -8,9 +8,6 @@
import org.w3c.dom.Node;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* This XML manager handles Wrapper nodes. Wrappers redirect to other VAST documents (which may
* in turn redirect to more wrappers). Wrappers can also contain impression trackers,
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/resource/CloseButtonDrawable.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/resource/CloseButtonDrawable.java
index 0cdb4fa73..123f33e34 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/resource/CloseButtonDrawable.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/mobileads/resource/CloseButtonDrawable.java
@@ -1,7 +1,6 @@
package com.mopub.mobileads.resource;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
public class CloseButtonDrawable extends BaseWidgetDrawable {
diff --git a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/network/AdRequest.java b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/network/AdRequest.java
index 45287b987..bd4ba50e7 100644
--- a/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/network/AdRequest.java
+++ b/mopub-sdk/mopub-sdk-base/src/main/java/com/mopub/network/AdRequest.java
@@ -243,6 +243,8 @@ protected Response parseNetworkResponse(final NetworkResponse networ
ResponseHeader.IMPRESSION_MIN_VISIBLE_PERCENT);
final String impressionVisibleMS = extractHeader(headers,
ResponseHeader.IMPRESSION_VISIBLE_MS);
+ final String impressionMinVisiblePx = extractHeader(headers,
+ ResponseHeader.IMPRESSION_MIN_VISIBLE_PX);
if (!TextUtils.isEmpty(impressionMinVisiblePercent)) {
serverExtras.put(DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT,
impressionMinVisiblePercent);
@@ -250,6 +252,9 @@ protected Response parseNetworkResponse(final NetworkResponse networ
if (!TextUtils.isEmpty(impressionVisibleMS)) {
serverExtras.put(DataKeys.IMPRESSION_VISIBLE_MS, impressionVisibleMS);
}
+ if (!TextUtils.isEmpty(impressionMinVisiblePx)) {
+ serverExtras.put(DataKeys.IMPRESSION_MIN_VISIBLE_PX, impressionMinVisiblePx);
+ }
}
if (AdType.VIDEO_NATIVE.equals(adTypeString)) {
serverExtras.put(DataKeys.PLAY_VISIBLE_PERCENT,
@@ -289,6 +294,14 @@ protected Response parseNetworkResponse(final NetworkResponse networ
extractHeader(headers, ResponseHeader.VIDEO_VIEWABILITY_TRACKERS));
}
+ // Banner imp tracking
+ if (AdFormat.BANNER.equals(mAdFormat)) {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS,
+ extractHeader(headers, ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_MS));
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS,
+ extractHeader(headers, ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_DIPS));
+ }
+
// Disable viewability vendors, if any
final String disabledViewabilityVendors = extractHeader(headers,
ResponseHeader.DISABLE_VIEWABILITY);
diff --git a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/BaseInterstitialActivity.java b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/BaseInterstitialActivity.java
index f5208bb00..3e4f0c175 100644
--- a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/BaseInterstitialActivity.java
+++ b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/BaseInterstitialActivity.java
@@ -18,27 +18,6 @@
abstract class BaseInterstitialActivity extends Activity {
@Nullable protected AdReport mAdReport;
-
- enum JavaScriptWebViewCallbacks {
- // The ad server appends these functions to the MRAID javascript to help with third party
- // impression tracking.
- WEB_VIEW_DID_APPEAR("webviewDidAppear();"),
- WEB_VIEW_DID_CLOSE("webviewDidClose();");
-
- private String mJavascript;
- private JavaScriptWebViewCallbacks(String javascript) {
- mJavascript = javascript;
- }
-
- protected String getJavascript() {
- return mJavascript;
- }
-
- protected String getUrl() {
- return "javascript:" + mJavascript;
- }
- }
-
@Nullable private CloseableLayout mCloseableLayout;
@Nullable private Long mBroadcastIdentifier;
diff --git a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MoPubActivity.java b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MoPubActivity.java
index 886624a9a..d5f51e1fe 100644
--- a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MoPubActivity.java
+++ b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MoPubActivity.java
@@ -31,8 +31,8 @@
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_DISMISS;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_FAIL;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_SHOW;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
import static com.mopub.mobileads.CustomEventInterstitial.CustomEventInterstitialListener;
import static com.mopub.mobileads.EventForwardingBroadcastReceiver.broadcastAction;
import static com.mopub.mobileads.HtmlWebViewClient.MOPUB_FAIL_LOAD;
@@ -167,14 +167,14 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
protected void onDestroy() {
- if (mHtmlInterstitialWebView != null) {
- mHtmlInterstitialWebView.loadUrl(WEB_VIEW_DID_CLOSE.getUrl());
- mHtmlInterstitialWebView.destroy();
- }
if (mExternalViewabilitySessionManager != null) {
mExternalViewabilitySessionManager.endDisplaySession();
mExternalViewabilitySessionManager = null;
}
+ if (mHtmlInterstitialWebView != null) {
+ mHtmlInterstitialWebView.loadUrl(WEB_VIEW_DID_CLOSE.getUrl());
+ mHtmlInterstitialWebView.destroy();
+ }
broadcastAction(this, getBroadcastIdentifier(), ACTION_INTERSTITIAL_DISMISS);
super.onDestroy();
}
diff --git a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MraidActivity.java b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MraidActivity.java
index bb9b44c49..a2bee5e71 100644
--- a/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MraidActivity.java
+++ b/mopub-sdk/mopub-sdk-interstitial/src/main/java/com/mopub/mobileads/MraidActivity.java
@@ -34,8 +34,8 @@
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_DISMISS;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_FAIL;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_SHOW;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
import static com.mopub.mobileads.EventForwardingBroadcastReceiver.broadcastAction;
import static com.mopub.mobileads.HtmlWebViewClient.MOPUB_FAIL_LOAD;
@@ -240,13 +240,14 @@ protected void onResume() {
@Override
protected void onDestroy() {
- if (mMraidController != null) {
- mMraidController.destroy();
- }
if (mExternalViewabilitySessionManager != null) {
mExternalViewabilitySessionManager.endDisplaySession();
mExternalViewabilitySessionManager = null;
}
+ if (mMraidController != null) {
+ mMraidController.destroy();
+ }
+
if (getBroadcastIdentifier()!= null) {
broadcastAction(this, getBroadcastIdentifier(), ACTION_INTERSTITIAL_DISMISS);
}
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionInterface.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionInterface.java
index 6f84021f3..6931c4148 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionInterface.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionInterface.java
@@ -8,6 +8,7 @@
*/
public interface ImpressionInterface {
int getImpressionMinPercentageViewed();
+ Integer getImpressionMinVisiblePx();
int getImpressionMinTimeViewed();
void recordImpression(View view);
boolean isImpressionRecorded();
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionTracker.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionTracker.java
index d7775eab0..d49896363 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionTracker.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/ImpressionTracker.java
@@ -117,7 +117,8 @@ public void addView(final View view, @NonNull final ImpressionInterface impressi
}
mTrackedViews.put(view, impressionInterface);
- mVisibilityTracker.addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ mVisibilityTracker.addView(view, impressionInterface.getImpressionMinPercentageViewed(),
+ impressionInterface.getImpressionMinVisiblePx());
}
public void removeView(final View view) {
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubAdAdapter.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubAdAdapter.java
index 18ec136cd..c1aaa849e 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubAdAdapter.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubAdAdapter.java
@@ -342,7 +342,7 @@ public View getView(final int position, final View view, final ViewGroup viewGro
mStreamAdPlacer.getOriginalPosition(position), view, viewGroup);
}
mViewPositionMap.put(resultView, position);
- mVisibilityTracker.addView(resultView, 0);
+ mVisibilityTracker.addView(resultView, 0, null);
return resultView;
}
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubCustomEventNative.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubCustomEventNative.java
index 02f935415..682097272 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubCustomEventNative.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubCustomEventNative.java
@@ -67,6 +67,16 @@ protected void loadNativeAd(@NonNull final Context context,
}
}
+ if (serverExtras.containsKey(DataKeys.IMPRESSION_MIN_VISIBLE_PX)) {
+ try {
+ moPubStaticNativeAd.setImpressionMinVisiblePx(Integer.parseInt(
+ serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX)));
+ } catch (final NumberFormatException e) {
+ MoPubLog.d("Unable to format min visible px: " +
+ serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX));
+ }
+ }
+
try {
moPubStaticNativeAd.loadAd();
} catch (IllegalArgumentException e) {
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubRecyclerAdapter.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubRecyclerAdapter.java
index 21d2e3d2d..7afb0337d 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubRecyclerAdapter.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/MoPubRecyclerAdapter.java
@@ -316,7 +316,6 @@ public void refreshAds(@NonNull String adUnitId,
loadAds(adUnitId, requestParameters);
} else {
MoPubLog.w("This LayoutManager can't be refreshed.");
- return;
}
}
@@ -420,7 +419,7 @@ public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int pos
}
mViewPositionMap.put(holder.itemView, position);
- mVisibilityTracker.addView(holder.itemView, 0);
+ mVisibilityTracker.addView(holder.itemView, 0, null);
//noinspection unchecked
mOriginalAdapter.onBindViewHolder(holder, mStreamAdPlacer.getOriginalPosition(position));
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/SpinningProgressView.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/SpinningProgressView.java
index 9fa5d9fff..ff2204401 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/SpinningProgressView.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/SpinningProgressView.java
@@ -3,7 +3,6 @@
import android.content.Context;
import android.graphics.Color;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/StaticNativeAd.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/StaticNativeAd.java
index d4ed19ef5..a2b90337b 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/StaticNativeAd.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/StaticNativeAd.java
@@ -35,6 +35,7 @@ public abstract class StaticNativeAd extends BaseNativeAd implements ImpressionI
private boolean mImpressionRecorded;
private int mImpressionMinTimeViewed;
private int mImpressionMinPercentageViewed;
+ private Integer mImpressionMinVisiblePx;
// Extras
@NonNull private final Map mExtras;
@@ -42,6 +43,7 @@ public abstract class StaticNativeAd extends BaseNativeAd implements ImpressionI
public StaticNativeAd() {
mImpressionMinTimeViewed = DEFAULT_IMPRESSION_MIN_TIME_VIEWED_MS;
mImpressionMinPercentageViewed = DEFAULT_IMPRESSION_MIN_PERCENTAGE_VIEWED;
+ mImpressionMinVisiblePx = null;
mExtras = new HashMap();
}
@@ -213,7 +215,7 @@ final public void setImpressionMinTimeViewed(final int impressionMinTimeViewed)
if (impressionMinTimeViewed > 0) {
mImpressionMinTimeViewed = impressionMinTimeViewed;
} else {
- MoPubLog.d("Ignoring non-positive impressionMinTimeViewed");
+ MoPubLog.d("Ignoring non-positive impressionMinTimeViewed: " + impressionMinTimeViewed);
}
}
@@ -227,7 +229,23 @@ final public void setImpressionMinPercentageViewed(final int impressionMinPercen
if (impressionMinPercentageViewed >= 0 && impressionMinPercentageViewed <= 100) {
mImpressionMinPercentageViewed = impressionMinPercentageViewed;
} else {
- MoPubLog.d("Ignoring impressionMinTimeViewed that's not a percent [0, 100]");
+ MoPubLog.d("Ignoring impressionMinTimeViewed that's not a percent [0, 100]: " +
+ impressionMinPercentageViewed);
+ }
+ }
+
+ /**
+ * Sets the minimum number of pixels of the ad to be on screen before impression trackers are
+ * fired. This must be an Integer greater than 0.
+ *
+ * @param impressionMinVisiblePx Number of pixels of an ad (ignored if negative or 0).
+ */
+ final public void setImpressionMinVisiblePx(@Nullable final Integer impressionMinVisiblePx) {
+ if (impressionMinVisiblePx != null && impressionMinVisiblePx > 0) {
+ mImpressionMinVisiblePx = impressionMinVisiblePx;
+ } else {
+ MoPubLog.d("Ignoring null or non-positive impressionMinVisiblePx: " +
+ impressionMinVisiblePx);
}
}
@@ -271,6 +289,17 @@ final public int getImpressionMinTimeViewed() {
return mImpressionMinTimeViewed;
}
+ /**
+ * Returns the minimum viewable number of pixels of the ad that must be onscreen for it to be
+ * considered visible. This value, if present and positive will override the min percentage.
+ * See {@link StaticNativeAd#getImpressionMinTimeViewed()} for additional impression
+ * tracking considerations.
+ */
+ @Override
+ final public Integer getImpressionMinVisiblePx() {
+ return mImpressionMinVisiblePx;
+ }
+
@Override
final public boolean isImpressionRecorded() {
return mImpressionRecorded;
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/TaskManager.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/TaskManager.java
index a13163c77..4b9df9eea 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/TaskManager.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/TaskManager.java
@@ -1,10 +1,8 @@
package com.mopub.nativeads;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import com.mopub.common.Preconditions;
-import com.mopub.common.Preconditions.NoThrow;
import java.util.Collections;
import java.util.HashMap;
diff --git a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/VisibilityTracker.java b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/VisibilityTracker.java
index 7453d2c4d..424e97d05 100644
--- a/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/VisibilityTracker.java
+++ b/mopub-sdk/mopub-sdk-native-static/src/main/java/com/mopub/nativeads/VisibilityTracker.java
@@ -54,6 +54,12 @@ static class TrackingInfo {
int mMaxInvisiblePercent;
long mAccessOrder;
View mRootView;
+
+ /**
+ * If this number is set, then use this as the minimum amount of the view seen before it is
+ * considered visible. This is in real pixels.
+ */
+ @Nullable Integer mMinVisiblePx;
}
// Views that are being tracked, mapped to the min viewable percentage
@@ -135,15 +141,19 @@ void setVisibilityTrackerListener(
/**
* Tracks the given view for visibility.
*/
- void addView(@NonNull final View view, final int minPercentageViewed) {
- addView(view, view, minPercentageViewed);
+ void addView(@NonNull final View view, final int minPercentageViewed,
+ @Nullable final Integer minVisiblePx) {
+ addView(view, view, minPercentageViewed, minVisiblePx);
}
- void addView(@NonNull View rootView, @NonNull final View view, final int minPercentageViewed) {
- addView(rootView, view, minPercentageViewed, minPercentageViewed);
+ void addView(@NonNull View rootView, @NonNull final View view, final int minPercentageViewed,
+ @Nullable final Integer minVisiblePx) {
+ addView(rootView, view, minPercentageViewed, minPercentageViewed, minVisiblePx);
}
- void addView(@NonNull View rootView, @NonNull final View view, final int minVisiblePercentageViewed, final int maxInvisiblePercentageViewed) {
+ void addView(@NonNull View rootView, @NonNull final View view,
+ final int minVisiblePercentageViewed, final int maxInvisiblePercentageViewed,
+ @Nullable final Integer minVisiblePx) {
setViewTreeObserver(view.getContext(), view);
// Find the view if already tracked
@@ -160,6 +170,7 @@ void addView(@NonNull View rootView, @NonNull final View view, final int minVisi
trackingInfo.mMinViewablePercent = minVisiblePercentageViewed;
trackingInfo.mMaxInvisiblePercent = maxInvisiblePercent;
trackingInfo.mAccessOrder = mAccessCounter;
+ trackingInfo.mMinVisiblePx = minVisiblePx;
// Trim the number of tracked views to a reasonable number
mAccessCounter++;
@@ -240,11 +251,14 @@ public void run() {
final View view = entry.getKey();
final int minPercentageViewed = entry.getValue().mMinViewablePercent;
final int maxInvisiblePercent = entry.getValue().mMaxInvisiblePercent;
+ final Integer minVisiblePx = entry.getValue().mMinVisiblePx;
final View rootView = entry.getValue().mRootView;
- if (mVisibilityChecker.isVisible(rootView, view, minPercentageViewed)) {
+ if (mVisibilityChecker.isVisible(rootView, view, minPercentageViewed,
+ minVisiblePx)) {
mVisibleViews.add(view);
- } else if (!mVisibilityChecker.isVisible(rootView, view, maxInvisiblePercent)){
+ } else if (!mVisibilityChecker.isVisible(rootView, view, maxInvisiblePercent,
+ null)) {
mInvisibleViews.add(view);
}
}
@@ -271,9 +285,11 @@ boolean hasRequiredTimeElapsed(final long startTimeMillis, final int minTimeView
}
/**
- * Whether the view is at least certain % visible
+ * Whether the view is at least certain amount visible. If the min pixel amount is set,
+ * use that. Otherwise, use the min percentage visible.
*/
- boolean isVisible(@Nullable final View rootView, @Nullable final View view, final int minPercentageViewed) {
+ boolean isVisible(@Nullable final View rootView, @Nullable final View view,
+ final int minPercentageViewed, @Nullable final Integer minVisiblePx) {
// ListView & GridView both call detachFromParent() for views that can be recycled for
// new data. This is one of the rare instances where a view will have a null parent for
// an extended period of time and will not be the main window.
@@ -297,6 +313,10 @@ boolean isVisible(@Nullable final View rootView, @Nullable final View view, fina
return false;
}
+ if (minVisiblePx != null && minVisiblePx > 0) {
+ return visibleViewArea >= minVisiblePx;
+ }
+
return 100 * visibleViewArea >= minPercentageViewed * totalViewArea;
}
}
diff --git a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/MoPubCustomEventVideoNative.java b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/MoPubCustomEventVideoNative.java
index dc9cefcb6..ac405b116 100644
--- a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/MoPubCustomEventVideoNative.java
+++ b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/MoPubCustomEventVideoNative.java
@@ -42,6 +42,7 @@
import static com.mopub.common.DataKeys.EVENT_DETAILS;
import static com.mopub.common.DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT;
+import static com.mopub.common.DataKeys.IMPRESSION_MIN_VISIBLE_PX;
import static com.mopub.common.DataKeys.IMPRESSION_VISIBLE_MS;
import static com.mopub.common.DataKeys.JSON_BODY_KEY;
import static com.mopub.common.DataKeys.MAX_BUFFER_MS;
@@ -305,6 +306,8 @@ public void onVastVideoConfigurationPrepared(@Nullable VastVideoConfig vastVideo
visibilityTrackingEvent.totalRequiredPlayTimeMs =
mVideoResponseHeaders.getImpressionVisibleMs();
visibilityTrackingEvents.add(visibilityTrackingEvent);
+ visibilityTrackingEvent.minimumVisiblePx =
+ mVideoResponseHeaders.getImpressionVisiblePx();
// VAST impression trackers
for (final VastTracker vastTracker : vastVideoConfig.getImpressionTrackers()) {
@@ -317,6 +320,8 @@ public void onVastVideoConfigurationPrepared(@Nullable VastVideoConfig vastVideo
vastImpressionTrackingEvent.totalRequiredPlayTimeMs =
mVideoResponseHeaders.getImpressionVisibleMs();
visibilityTrackingEvents.add(vastImpressionTrackingEvent);
+ vastImpressionTrackingEvent.minimumVisiblePx =
+ mVideoResponseHeaders.getImpressionVisiblePx();
}
// Visibility tracking event from http response Vast payload
@@ -435,7 +440,8 @@ public void render(@NonNull MediaLayout mediaLayout) {
mVideoVisibleTracking.addView(mRootView,
mediaLayout,
mVideoResponseHeaders.getPlayVisiblePercent(),
- mVideoResponseHeaders.getPauseVisiblePercent());
+ mVideoResponseHeaders.getPauseVisiblePercent(),
+ mVideoResponseHeaders.getImpressionVisiblePx());
mMediaLayout = mediaLayout;
mMediaLayout.initForVideo();
@@ -905,14 +911,13 @@ static class VideoResponseHeaders {
private int mImpressionMinVisiblePercent;
private int mImpressionVisibleMs;
private int mMaxBufferMs;
+ private Integer mImpressionVisiblePx;
private JSONObject mVideoTrackers;
VideoResponseHeaders(@NonNull final Map serverExtras) {
try {
mPlayVisiblePercent = Integer.parseInt(serverExtras.get(PLAY_VISIBLE_PERCENT));
mPauseVisiblePercent = Integer.parseInt(serverExtras.get(PAUSE_VISIBLE_PERCENT));
- mImpressionMinVisiblePercent =
- Integer.parseInt(serverExtras.get(IMPRESSION_MIN_VISIBLE_PERCENT));
mImpressionVisibleMs = Integer.parseInt(serverExtras.get(IMPRESSION_VISIBLE_MS));
mMaxBufferMs = Integer.parseInt(serverExtras.get(MAX_BUFFER_MS));
mHeadersAreValid = true;
@@ -920,6 +925,25 @@ static class VideoResponseHeaders {
mHeadersAreValid = false;
}
+ final String impressionVisiblePxString = serverExtras.get(IMPRESSION_MIN_VISIBLE_PX);
+ if (!TextUtils.isEmpty(impressionVisiblePxString)) {
+ try {
+ mImpressionVisiblePx = Integer.parseInt(impressionVisiblePxString);
+ } catch (NumberFormatException e) {
+ MoPubLog.d("Unable to parse impression min visible px from server extras.");
+ }
+ }
+ try {
+ mImpressionMinVisiblePercent =
+ Integer.parseInt(serverExtras.get(IMPRESSION_MIN_VISIBLE_PERCENT));
+ } catch (NumberFormatException e) {
+ MoPubLog.d("Unable to parse impression min visible percent from server extras.");
+ if (mImpressionVisiblePx == null || mImpressionVisiblePx < 0) {
+ mHeadersAreValid = false;
+ }
+ }
+
+
final String videoTrackers = serverExtras.get(VIDEO_TRACKERS_KEY);
if (TextUtils.isEmpty(videoTrackers)) {
return;
@@ -957,6 +981,11 @@ int getMaxBufferMs() {
return mMaxBufferMs;
}
+ @Nullable
+ Integer getImpressionVisiblePx() {
+ return mImpressionVisiblePx;
+ }
+
JSONObject getVideoTrackers() {
return mVideoTrackers;
}
diff --git a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeFullScreenVideoView.java b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeFullScreenVideoView.java
index 0b7c6d0b2..eb3a94fff 100644
--- a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeFullScreenVideoView.java
+++ b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeFullScreenVideoView.java
@@ -422,7 +422,7 @@ public void setColorFilter(ColorFilter cf) { }
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
- };
+ }
@Deprecated
@VisibleForTesting
diff --git a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeVideoController.java b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeVideoController.java
index 94f66ef71..d2e8f3e0d 100644
--- a/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeVideoController.java
+++ b/mopub-sdk/mopub-sdk-native-video/src/main/java/com/mopub/nativeads/NativeVideoController.java
@@ -42,6 +42,7 @@
import com.mopub.common.event.Event;
import com.mopub.common.event.EventDetails;
import com.mopub.common.event.MoPubEvents;
+import com.mopub.common.logging.MoPubLog;
import com.mopub.mobileads.RepeatingHandlerRunnable;
import com.mopub.mobileads.VastTracker;
import com.mopub.mobileads.VastVideoConfig;
@@ -310,6 +311,11 @@ public void onLoadingChanged(boolean isLoading) {}
@Override
public void onPlayerStateChanged(final boolean playWhenReady, final int newState) {
if (newState == STATE_ENDED && mFinalFrame == null) {
+ if (mExoPlayer == null || mSurface == null || mTextureView == null) {
+ MoPubLog.w("onPlayerStateChanged called afer view has been recycled.");
+ return;
+ }
+
mFinalFrame = new BitmapDrawable(mContext.getResources(), mTextureView.getBitmap());
mNativeVideoProgressRunnable.requestStop();
}
@@ -520,6 +526,7 @@ interface OnTrackedStrategy {
int totalRequiredPlayTimeMs;
int totalQualifiedPlayCounter;
boolean isTracked;
+ Integer minimumVisiblePx;
}
static class NativeVideoProgressRunnable extends RepeatingHandlerRunnable {
@@ -607,7 +614,7 @@ void checkImpressionTrackers(final boolean forceTrigger) {
continue;
}
if (forceTrigger || mVisibilityChecker.isVisible(mTextureView, mTextureView,
- event.minimumPercentageVisible)) {
+ event.minimumPercentageVisible, event.minimumVisiblePx)) {
event.totalQualifiedPlayCounter += mUpdateIntervalMillis;
if (forceTrigger ||
event.totalQualifiedPlayCounter >= event.totalRequiredPlayTimeMs) {
diff --git a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedAd.java b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedAd.java
index 62dd02dbf..7298bf8cb 100644
--- a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedAd.java
+++ b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedAd.java
@@ -25,6 +25,7 @@ public abstract class MoPubRewardedAd extends CustomEventRewardedAd {
private boolean mIsLoaded;
@Nullable private String mRewardedAdCurrencyName;
private int mRewardedAdCurrencyAmount;
+ @Nullable protected String mAdUnitId;
@Nullable
@Override
@@ -84,6 +85,13 @@ protected void loadWithSdkInitialized(@NonNull final Activity activity,
MoPubReward.DEFAULT_REWARD_AMOUNT);
mRewardedAdCurrencyAmount = MoPubReward.DEFAULT_REWARD_AMOUNT;
}
+
+ final Object adUnitId = localExtras.get(DataKeys.AD_UNIT_ID_KEY);
+ if (adUnitId instanceof String) {
+ mAdUnitId = (String) adUnitId;
+ } else {
+ MoPubLog.d("Unable to set ad unit for rewarded ad.");
+ }
}
@Override
diff --git a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedPlayable.java b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedPlayable.java
index 6af773a2d..963ec1cba 100644
--- a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedPlayable.java
+++ b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedPlayable.java
@@ -16,7 +16,7 @@
*/
public class MoPubRewardedPlayable extends MoPubRewardedAd {
- @NonNull private static final String MOPUB_REWARDED_PLAYABLE_ID = "mopub_rewarded_playable_id";
+ @NonNull static final String MOPUB_REWARDED_PLAYABLE_ID = "mopub_rewarded_playable_id";
@Nullable private RewardedMraidInterstitial mRewardedMraidInterstitial;
public MoPubRewardedPlayable() {
@@ -40,7 +40,7 @@ protected void loadWithSdkInitialized(@NonNull final Activity activity,
@NonNull
@Override
protected String getAdNetworkId() {
- return MOPUB_REWARDED_PLAYABLE_ID;
+ return mAdUnitId != null ? mAdUnitId : MOPUB_REWARDED_PLAYABLE_ID;
}
@Override
diff --git a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedVideo.java b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedVideo.java
index e85a0e92d..ca70f8b3f 100644
--- a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedVideo.java
+++ b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/MoPubRewardedVideo.java
@@ -15,7 +15,7 @@
*/
public class MoPubRewardedVideo extends MoPubRewardedAd {
- @NonNull private static final String MOPUB_REWARDED_VIDEO_ID = "mopub_rewarded_video_id";
+ @NonNull static final String MOPUB_REWARDED_VIDEO_ID = "mopub_rewarded_video_id";
@Nullable private RewardedVastVideoInterstitial mRewardedVastVideoInterstitial;
@@ -26,7 +26,7 @@ public MoPubRewardedVideo() {
@NonNull
@Override
protected String getAdNetworkId() {
- return MOPUB_REWARDED_VIDEO_ID;
+ return mAdUnitId != null ? mAdUnitId : MOPUB_REWARDED_VIDEO_ID;
}
@Override
diff --git a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/RewardedMraidActivity.java b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/RewardedMraidActivity.java
index a5bf95a00..5855cc691 100644
--- a/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/RewardedMraidActivity.java
+++ b/mopub-sdk/mopub-sdk-rewardedvideo/src/main/java/com/mopub/mobileads/RewardedMraidActivity.java
@@ -26,8 +26,8 @@
import static com.mopub.common.DataKeys.SHOULD_REWARD_ON_CLICK_KEY;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_CLICK;
import static com.mopub.common.IntentActions.ACTION_INTERSTITIAL_FAIL;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
-import static com.mopub.mobileads.BaseInterstitialActivity.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_APPEAR;
+import static com.mopub.common.util.JavaScriptWebViewCallbacks.WEB_VIEW_DID_CLOSE;
import static com.mopub.mobileads.EventForwardingBroadcastReceiver.broadcastAction;
public class RewardedMraidActivity extends MraidActivity {
diff --git a/mopub-sdk/publisher.gradle b/mopub-sdk/publisher.gradle
index 2744493a1..9c40f84aa 100644
--- a/mopub-sdk/publisher.gradle
+++ b/mopub-sdk/publisher.gradle
@@ -23,7 +23,7 @@ android.libraryVariants.all { variant ->
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDir
- artifacts.add('archives', task);
+ artifacts.add('archives', task)
}
android.libraryVariants.all { variant ->
diff --git a/mopub-sdk/shared-build.gradle b/mopub-sdk/shared-build.gradle
index d5d31f36f..7a2d33404 100644
--- a/mopub-sdk/shared-build.gradle
+++ b/mopub-sdk/shared-build.gradle
@@ -11,7 +11,7 @@ repositories {
}
project.group = 'com.mopub'
-project.version = '4.18.0'
+project.version = '4.19.0'
android {
compileSdkVersion 26
@@ -20,7 +20,7 @@ android {
useLibrary 'org.apache.http.legacy'
defaultConfig {
- versionCode 56
+ versionCode 57
versionName version
minSdkVersion 16
targetSdkVersion 26
diff --git a/mopub-sdk/src/main/resources/fabric/com.mopub.sdk.android.mopub.properties b/mopub-sdk/src/main/resources/fabric/com.mopub.sdk.android.mopub.properties
index 6b29df7ac..208a50026 100644
--- a/mopub-sdk/src/main/resources/fabric/com.mopub.sdk.android.mopub.properties
+++ b/mopub-sdk/src/main/resources/fabric/com.mopub.sdk.android.mopub.properties
@@ -1,3 +1,3 @@
fabric-identifier=com.mopub.sdk.android:mopub
-fabric-version=4.18.0+kit
+fabric-version=4.19.0+kit
fabric-build-type=source
diff --git a/mopub-sdk/src/test/java/com/mopub/common/UrlHandlerTest.java b/mopub-sdk/src/test/java/com/mopub/common/UrlHandlerTest.java
index 66ccbdd2b..909292e8f 100644
--- a/mopub-sdk/src/test/java/com/mopub/common/UrlHandlerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/common/UrlHandlerTest.java
@@ -13,7 +13,6 @@
import com.mopub.network.Networking;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/BannerVisibilityTrackerTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/BannerVisibilityTrackerTest.java
new file mode 100644
index 000000000..73423b795
--- /dev/null
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/BannerVisibilityTrackerTest.java
@@ -0,0 +1,310 @@
+package com.mopub.mobileads;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+
+import com.mopub.common.test.support.SdkTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowSystemClock;
+
+import static android.view.ViewTreeObserver.OnPreDrawListener;
+import static com.mopub.mobileads.BannerVisibilityTracker.BannerVisibilityChecker;
+import static com.mopub.mobileads.BannerVisibilityTracker.BannerVisibilityTrackerListener;
+import static org.fest.assertions.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SdkTestRunner.class)
+@Config(constants = BuildConfig.class)
+public class BannerVisibilityTrackerTest {
+ private static final int MIN_VISIBLE_DIPS = 1;
+ private static final int MIN_VISIBLE_MILLIS = 0;
+
+ private Activity activity;
+ private BannerVisibilityTracker subject;
+ private BannerVisibilityChecker visibilityChecker;
+ private Handler visibilityHandler;
+
+ private View mockView;
+ @Mock
+ private BannerVisibilityTrackerListener visibilityTrackerListener;
+
+ @Before
+ public void setUp() throws Exception {
+ activity = Robolectric.buildActivity(Activity.class).create().get();
+ mockView = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
+ subject = new BannerVisibilityTracker(activity, mockView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+
+ subject.setBannerVisibilityTrackerListener(visibilityTrackerListener);
+
+ visibilityChecker = subject.getBannerVisibilityChecker();
+ visibilityHandler = subject.getVisibilityHandler();
+
+ // XXX We need this to ensure that our SystemClock starts
+ ShadowSystemClock.uptimeMillis();
+ }
+
+ @Test
+ public void constructor_shouldSetOnPreDrawListenerForDecorView() throws Exception {
+ Activity spyActivity = spy(Robolectric.buildActivity(Activity.class).create().get());
+ Window window = mock(Window.class);
+ View decorView = mock(View.class);
+ ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+
+ when(spyActivity.getWindow()).thenReturn(window);
+ when(window.getDecorView()).thenReturn(decorView);
+ when(decorView.findViewById(anyInt())).thenReturn(decorView);
+ when(decorView.getViewTreeObserver()).thenReturn(viewTreeObserver);
+ when(viewTreeObserver.isAlive()).thenReturn(true);
+
+ subject = new BannerVisibilityTracker(spyActivity, mockView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+ assertThat(subject.mOnPreDrawListener).isNotNull();
+ verify(viewTreeObserver).addOnPreDrawListener(subject.mOnPreDrawListener);
+ assertThat(subject.mWeakViewTreeObserver.get()).isEqualTo(viewTreeObserver);
+ }
+
+ @Test
+ public void constructor_withNonAliveViewTreeObserver_shouldNotSetOnPreDrawListenerForDecorView() throws Exception {
+ Activity mockActivity = mock(Activity.class);
+ Window window = mock(Window.class);
+ View decorView = mock(View.class);
+ ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+
+ when(mockActivity.getWindow()).thenReturn(window);
+ when(window.getDecorView()).thenReturn(decorView);
+ when(decorView.getViewTreeObserver()).thenReturn(viewTreeObserver);
+ when(viewTreeObserver.isAlive()).thenReturn(false);
+
+ subject = new BannerVisibilityTracker(mockActivity, mockView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+ verify(viewTreeObserver, never()).addOnPreDrawListener(subject.mOnPreDrawListener);
+ assertThat(subject.mWeakViewTreeObserver.get()).isNull();
+ }
+
+ @Test
+ public void constructor_withApplicationContext_shouldNotSetOnPreDrawListener() {
+ subject = new BannerVisibilityTracker(activity.getApplicationContext(), mockView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+
+ assertThat(subject.mWeakViewTreeObserver.get()).isNull();
+ }
+
+ @Test
+ public void constructor_withViewTreeObserverNotSet_shouldSetViewTreeObserver() {
+ ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+ View rootView = mock(View.class);
+
+ when(mockView.getContext()).thenReturn(activity.getApplicationContext());
+ when(mockView.getRootView()).thenReturn(rootView);
+ when(rootView.getViewTreeObserver()).thenReturn(viewTreeObserver);
+ when(viewTreeObserver.isAlive()).thenReturn(true);
+
+ subject = new BannerVisibilityTracker(activity.getApplicationContext(), rootView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+ assertThat(subject.mWeakViewTreeObserver.get()).isEqualTo(viewTreeObserver);
+ }
+
+ @Test
+ public void destroy_shouldRemoveListenerFromDecorView() throws Exception {
+ Activity spyActivity = spy(Robolectric.buildActivity(Activity.class).create().get());
+ Window window = mock(Window.class);
+ View decorView = mock(View.class);
+ ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
+
+ when(spyActivity.getWindow()).thenReturn(window);
+ when(window.getDecorView()).thenReturn(decorView);
+ when(decorView.findViewById(anyInt())).thenReturn(decorView);
+ when(decorView.getViewTreeObserver()).thenReturn(viewTreeObserver);
+ when(viewTreeObserver.isAlive()).thenReturn(true);
+
+ subject = new BannerVisibilityTracker(spyActivity, mockView, mockView, MIN_VISIBLE_DIPS, MIN_VISIBLE_MILLIS);
+ subject.destroy();
+
+ assertThat(visibilityHandler.hasMessages(0)).isFalse();
+ assertThat(subject.isVisibilityScheduled()).isFalse();
+ verify(viewTreeObserver).removeOnPreDrawListener(any(OnPreDrawListener.class));
+ assertThat(subject.mWeakViewTreeObserver.get()).isNull();
+ assertThat(subject.getBannerVisibilityTrackerListener()).isNull();
+ }
+
+ // BannerVisibilityRunnable Tests
+ @Test
+ public void visibilityRunnable_run_withViewVisibleForAtLeastMinDuration_shouldCallOnVisibilityChangedCallback_shouldMarkImpTrackerAsFired_shouldNotScheduleVisibilityCheck() throws Exception {
+ subject.new BannerVisibilityRunnable().run();
+
+ verify(visibilityTrackerListener).onVisibilityChanged();
+ assertThat(subject.isImpTrackerFired()).isTrue();
+ assertThat(subject.isVisibilityScheduled()).isFalse();
+ }
+
+ @Test
+ public void visibilityRunnable_run_withViewNotVisible_shouldNotCallOnVisibilityChangedCallback_shouldNotMarkImpTrackerAsFired_shouldScheduleVisibilityCheck() throws Exception {
+ when(mockView.getVisibility()).thenReturn(View.INVISIBLE);
+
+ subject.new BannerVisibilityRunnable().run();
+
+ verify(visibilityTrackerListener, never()).onVisibilityChanged();
+ assertThat(subject.isImpTrackerFired()).isFalse();
+ assertThat(subject.isVisibilityScheduled()).isTrue();
+ }
+
+ @Test
+ public void visibilityRunnable_run_witViewVisibleForLessThanMinDuration_shouldNotCallOnVisibilityChangedCallback_shouldNotMarkImpTrackerAsFired_shouldScheduleVisibilityCheck() throws Exception {
+ subject = new BannerVisibilityTracker(activity, mockView, mockView, 1, 1000);
+ subject.new BannerVisibilityRunnable().run();
+
+ verify(visibilityTrackerListener, never()).onVisibilityChanged();
+ assertThat(subject.isImpTrackerFired()).isFalse();
+ assertThat(subject.isVisibilityScheduled()).isTrue();
+ }
+
+ // BannerVisibilityChecker Tests
+ @Test
+ public void hasRequiredTimeElapsed_withStartTimeNotSetYet_shouldReturnFalse() throws Exception {
+ assertThat(visibilityChecker.hasRequiredTimeElapsed()).isFalse();
+ }
+
+ @Test
+ public void hasRequiredTimeElapsed_withStartTimeSet_withElapsedTimeGreaterThanMinTimeViewed_shouldReturnTrue() throws Exception {
+ visibilityChecker.setStartTimeMillis();
+
+ // minVisibleMillis is 0 ms as defined by constant MIN_VISIBLE_MILLIS
+ assertThat(visibilityChecker.hasRequiredTimeElapsed()).isTrue();
+ }
+
+ @Test
+ public void hasRequiredTimeElapsed_withStartTimeSet_withElapsedTimeLessThanMinTimeViewed_shouldReturnFalse() throws Exception {
+ subject = new BannerVisibilityTracker(activity, mockView, mockView, 1, 1000);
+ visibilityChecker = subject.getBannerVisibilityChecker();
+ visibilityChecker.setStartTimeMillis();
+
+ // minVisibleMillis is 1 sec, should return false since we are checking immediately before 1 sec elapses
+ assertThat(visibilityChecker.hasRequiredTimeElapsed()).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenParentIsNull_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 100, 100, 100, 100, false, true);
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewIsOffScreen_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, false);
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewIsEntirelyOnScreen_shouldReturnTrue() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isTrue();
+ }
+
+ @Test
+ public void isVisible_whenViewHasMoreVisibleDipsThanMinVisibleDips_shouldReturnTrue() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 1, 2, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isTrue();
+ }
+
+ @Test
+ public void isVisible_whenViewHasExactlyMinVisibleDips_shouldReturnTrue() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 1, 1, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isTrue();
+ }
+
+ @Test
+ public void isVisible_whenViewHasLessVisibleDipsThanMinVisibleDips_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 0, 1, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenVisibleAreaIsZero_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 0, 0, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewIsInvisibleOrGone_shouldReturnFalse() throws Exception {
+ View view = createViewMock(View.INVISIBLE, 100, 100, 100, 100, true, true);
+ assertThat(visibilityChecker.isVisible(view, view)).isFalse();
+
+ reset(view);
+ view = createViewMock(View.GONE, 100, 100, 100, 100, true, true);
+ assertThat(visibilityChecker.isVisible(view, view)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewHasZeroWidth_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 100, 100, 0, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewHasZeroHeight_shouldReturnFalse() throws Exception {
+ mockView = createViewMock(View.VISIBLE, 100, 100, 100, 0, true, true);
+
+ assertThat(visibilityChecker.isVisible(mockView, mockView)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenViewIsNull_shouldReturnFalse() throws Exception {
+ assertThat(visibilityChecker.isVisible(null, null)).isFalse();
+ }
+
+ static View createViewMock(final int visibility,
+ final int visibleWidth,
+ final int visibleHeight,
+ final int viewWidth,
+ final int viewHeight,
+ final boolean isParentSet,
+ final boolean isOnScreen) {
+ View view = mock(View.class);
+ when(view.getContext()).thenReturn(new Activity());
+ when(view.getVisibility()).thenReturn(visibility);
+
+ when(view.getGlobalVisibleRect(any(Rect.class)))
+ .thenAnswer(new Answer() {
+ @Override
+ public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Rect rect = (Rect) args[0];
+ rect.set(0, 0, visibleWidth, visibleHeight);
+ return isOnScreen;
+ }
+ });
+
+ when(view.getWidth()).thenReturn(viewWidth);
+ when(view.getHeight()).thenReturn(viewHeight);
+
+ if (isParentSet) {
+ when(view.getParent()).thenReturn(mock(ViewParent.class));
+ }
+
+ when(view.getViewTreeObserver()).thenCallRealMethod();
+
+ return view;
+ }
+}
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/CustomEventBannerAdapterTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/CustomEventBannerAdapterTest.java
index 1dd64a581..18dd5961e 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/CustomEventBannerAdapterTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/CustomEventBannerAdapterTest.java
@@ -55,7 +55,6 @@ public class CustomEventBannerAdapterTest {
@Before
public void setUp() throws Exception {
-
when(moPubView.getAdTimeoutDelay()).thenReturn(null);
when(moPubView.getAdWidth()).thenReturn(320);
when(moPubView.getAdHeight()).thenReturn(50);
@@ -128,7 +127,6 @@ public void timeout_withNonNullAdTimeoutDelay_shouldSignalFailureAndInvalidateWi
assertThat(subject.isInvalidated()).isTrue();
}
-
@Test
public void loadAd_shouldPropagateLocationInLocalExtras() throws Exception {
Location expectedLocation = new Location("");
@@ -210,7 +208,6 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
subject.loadAd();
}
-
@Test
public void loadAd_whenCallingOnBannerFailed_shouldCancelExistingTimeoutRunnable() throws Exception {
ShadowLooper.pauseMainLooper();
@@ -241,25 +238,77 @@ public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
}
@Test
- public void onBannerLoaded_shouldSignalMoPubView() throws Exception {
+ public void onBannerLoaded_whenViewIsHtmlBannerWebView_shouldNotTrackImpression() throws Exception {
+ View mockHtmlBannerWebView = mock(HtmlBannerWebView.class);
+ subject.onBannerLoaded(mockHtmlBannerWebView);
+
+ verify(moPubView).nativeAdLoaded();
+ verify(moPubView).setAdContentView(eq(mockHtmlBannerWebView));
+ verify(moPubView, never()).trackNativeImpression();
+
+ // Since there are no visibility imp tracking headers, imp tracking should not be enabled.
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ assertThat(subject.getVisibilityTracker()).isNull();
+ }
+
+ @Test
+ public void onBannerLoaded_whenViewIsNotHtmlBannerWebView_shouldSignalMoPubView() throws Exception {
View view = new View(Robolectric.buildActivity(Activity.class).create().get());
subject.onBannerLoaded(view);
verify(moPubView).nativeAdLoaded();
verify(moPubView).setAdContentView(eq(view));
verify(moPubView).trackNativeImpression();
+
+ // Since there are no visibility imp tracking headers, imp tracking should not be enabled.
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ assertThat(subject.getVisibilityTracker()).isNull();
}
@Test
- public void onBannerLoaded_whenViewIsHtmlBannerWebView_shouldNotTrackImpression() throws Exception {
+ public void onBannerLoaded_whenViewIsHtmlBannerWebView_withVisibilityImpressionTrackingEnabled_shouldSetUpVisibilityTrackerWithListener_shouldNotTrackNativeImpressionImmediately() {
View mockHtmlBannerWebView = mock(HtmlBannerWebView.class);
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "1");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "0");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
subject.onBannerLoaded(mockHtmlBannerWebView);
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(1);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(0);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isTrue();
+ assertThat(subject.getVisibilityTracker()).isNotNull();
+ assertThat(subject.getVisibilityTracker().getBannerVisibilityTrackerListener()).isNotNull();
verify(moPubView).nativeAdLoaded();
verify(moPubView).setAdContentView(eq(mockHtmlBannerWebView));
verify(moPubView, never()).trackNativeImpression();
}
+ @Test
+ public void onBannerLoaded_whenViewIsNotHtmlBannerWebView_withVisibilityImpressionTrackingEnabled_shouldSetUpVisibilityTrackerWithListener_shouldNotTrackNativeImpressionImmediately() {
+ View view = new View(Robolectric.buildActivity(Activity.class).create().get());
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "1");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "0");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+ subject.onBannerLoaded(view);
+
+ // When visibility impression tracking is enabled, regardless of whether the banner view is
+ // HtmlBannerWebView or not, the behavior should be the same.
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(1);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(0);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isTrue();
+ assertThat(subject.getVisibilityTracker()).isNotNull();
+ assertThat(subject.getVisibilityTracker().getBannerVisibilityTrackerListener()).isNotNull();
+ verify(moPubView).nativeAdLoaded();
+ verify(moPubView).setAdContentView(eq(view));
+ verify(moPubView, never()).trackNativeImpression();
+ }
+
@Test
public void onBannerFailed_shouldLoadFailUrl() throws Exception {
subject.onBannerFailed(ADAPTER_CONFIGURATION_ERROR);
@@ -347,4 +396,106 @@ public void invalidate_shouldCauseBannerListenerMethodsToDoNothing() throws Exce
verify(moPubView, never()).adClosed();
verify(moPubView, never()).registerClick();
}
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_whenMissingInServerExtras_shouldUseDefaultValues_shouldNotEnableVisibilityImpressionTracking() {
+ // If headers are missing, use default values
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withBothValuesNonInteger_shouldUseDefaultValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, null);
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ // Both header values must be Integers in order to be parsed
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withNonIntegerMinVisibleDipsValue_shouldUseDefaultValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, null);
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "0");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ // Both header values must be Integers in order to be parsed
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withNonIntegerMinVisibleMsValue_shouldUseDefaultValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "1");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ // Both header values must be Integers in order to be parsed
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(Integer.MIN_VALUE);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withBothValuesValid_shouldParseValues_shouldEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "1");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "0");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(1);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(0);
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isTrue();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withBothValuesInvalid_shouldParseValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "0");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "-1");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(0);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(-1);
+
+ // ImpressionMinVisibleDips must be > 0 AND ImpressionMinVisibleMs must be >= 0 in order to
+ // enable viewable impression tracking
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withInvalidMinVisibleDipsValue_shouldParseValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "0");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "0");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(0);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(0);
+
+ // ImpressionMinVisibleDips must be > 0 in order to enable viewable impression tracking
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
+
+ @Test
+ public void parseBannerImpressionTrackingHeaders_withInvalidMinVisibleMsValue_shouldParseValues_shouldNotEnableVisibilityImpressionTracking() {
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS, "1");
+ serverExtras.put(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS, "-1");
+
+ subject = new CustomEventBannerAdapter(moPubView, CLASS_NAME, serverExtras, BROADCAST_IDENTIFIER, mockAdReport);
+
+ assertThat(subject.getImpressionMinVisibleDips()).isEqualTo(1);
+ assertThat(subject.getImpressionMinVisibleMs()).isEqualTo(-1);
+
+ // ImpressionMinVisibleMs must be >= 0 in order to enable viewable impression tracking
+ assertThat(subject.isVisibilityImpressionTrackingEnabled()).isFalse();
+ }
}
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/HtmlBannerTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/HtmlBannerTest.java
index 4b0d48b1d..f824f149e 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/HtmlBannerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/HtmlBannerTest.java
@@ -123,4 +123,13 @@ public void loadBanner_shouldCauseServerDimensionsToBeHonoredWhenLayingOutView()
assertThat(layoutParams.height).isEqualTo(50);
assertThat(layoutParams.gravity).isEqualTo(Gravity.CENTER);
}
+
+ @Test
+ public void trackMpxAndThirdPartyImpressions_shouldFireJavascriptWebViewDidAppear() throws Exception {
+ subject.loadBanner(context, customEventBannerListener, localExtras, serverExtras);
+ subject.trackMpxAndThirdPartyImpressions();
+
+ verify(htmlBannerWebView).loadHtmlResponse(responseBody);
+ verify(htmlBannerWebView).loadUrl(eq("javascript:webviewDidAppear();"));
+ }
}
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedPlayableTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedPlayableTest.java
index 479fe1d47..1b4708c87 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedPlayableTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedPlayableTest.java
@@ -15,7 +15,6 @@
import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
@@ -61,7 +60,7 @@ public void onInvalidate_withNullRewardedMraidActivity_shouldNotInvalidateReward
@Test
public void loadWithSdkInitialized_withCorrectLocalExtras_shouldLoadVastVideoInterstitial() throws Exception {
subject.setRewardedMraidInterstitial(mockRewardedMraidInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
final Map serverExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "currencyName");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "10");
@@ -79,6 +78,25 @@ public void loadWithSdkInitialized_withCorrectLocalExtras_shouldLoadVastVideoInt
assertThat(subject.getRewardedAdCurrencyAmount()).isEqualTo(10);
}
+ @Test
+ public void loadWithSdkInitialized_withAdUnitId_shouldSetAdNetworkId() throws Exception {
+ final Map localExtras = new HashMap();
+ localExtras.put(DataKeys.AD_UNIT_ID_KEY, "adUnit");
+
+ subject.loadWithSdkInitialized(activity, localExtras, new HashMap());
+
+ assertThat(subject.getAdNetworkId()).isEqualTo("adUnit");
+ }
+
+ @Test
+ public void loadWithSdkInitialized_withNoAdUnitId_shouldUseDefaultAdNetworkId() throws Exception {
+ subject.loadWithSdkInitialized(activity, new HashMap(),
+ new HashMap());
+
+ assertThat(subject.getAdNetworkId()).isEqualTo(
+ MoPubRewardedPlayable.MOPUB_REWARDED_PLAYABLE_ID);
+ }
+
@Test
public void show_withMraidLoaded_shouldShowRewardedMraidInterstitial() {
subject.setRewardedMraidInterstitial(mockRewardedMraidInterstitial);
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedVideoTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedVideoTest.java
index ecb80584a..bfeb5d34f 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedVideoTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubRewardedVideoTest.java
@@ -15,7 +15,6 @@
import java.util.HashMap;
import java.util.Map;
-import java.util.TreeMap;
import static com.mopub.common.Constants.FOUR_HOURS_MILLIS;
import static com.mopub.mobileads.MoPubErrorCode.EXPIRED;
@@ -67,12 +66,12 @@ public void onInvalidate_withNullVastVideoInterstitial_shouldNotInvalidateVastVi
@Test
public void loadWithSdkInitialized_withLocalExtrasIncomplete_shouldLoadVastVideoInterstitial() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- subject.loadWithSdkInitialized(activity, new TreeMap(),
+ subject.loadWithSdkInitialized(activity, new HashMap(),
new HashMap());
verify(mockRewardedVastVideoInterstitial).loadInterstitial(eq(activity), any(
CustomEventInterstitial.CustomEventInterstitialListener.class),
- eq(new TreeMap()),
+ eq(new HashMap()),
eq(new HashMap()));
verifyNoMoreInteractions(mockRewardedVastVideoInterstitial);
assertThat(subject.getRewardedAdCurrencyName()).isEqualTo("");
@@ -82,7 +81,7 @@ public void loadWithSdkInitialized_withLocalExtrasIncomplete_shouldLoadVastVideo
@Test
public void loadWithSdkInitialized_withRewardedVideoCurrencyNameIncorrectType_shouldLoadVastVideoInterstitial_shouldSetCurrencyNameToEmptyString() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, new Object());
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "10");
@@ -100,7 +99,7 @@ public void loadWithSdkInitialized_withRewardedVideoCurrencyNameIncorrectType_sh
@Test
public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountIncorrectType_shouldLoadVastVideoInterstitial_shouldSetCurrencyAmountToZero() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "currencyName");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, new Object());
@@ -118,7 +117,7 @@ public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountIncorrectType_
@Test
public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountNotInteger_shouldLoadVastVideoInterstitial_shouldSetCurrencyAmountToZero() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "currencyName");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "foo");
@@ -136,7 +135,7 @@ public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountNotInteger_sho
@Test
public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountNegative_shouldLoadVastVideoInterstitial_shouldSetCurrencyAmountToZero() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "currencyName");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "-42");
@@ -154,7 +153,7 @@ public void loadWithSdkInitialized_withRewardedVideoCurrencyAmountNegative_shoul
@Test
public void loadWithSdkInitialized_withCorrectLocalExtras_shouldLoadVastVideoInterstitial() throws Exception {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
final Map serverExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "currencyName");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "10");
@@ -174,7 +173,7 @@ public void loadWithSdkInitialized_withEmptyCurrencyName_withNegativeCurrencyAmo
// We pass whatever was sent to this custom event to the app as long as it exists, but
// if the currency value is negative, set it to 0
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
- final Map localExtras = new TreeMap();
+ final Map localExtras = new HashMap();
final Map serverExtras = new HashMap();
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_NAME_KEY, "");
localExtras.put(DataKeys.REWARDED_AD_CURRENCY_AMOUNT_STRING_KEY, "-10");
@@ -189,6 +188,24 @@ public void loadWithSdkInitialized_withEmptyCurrencyName_withNegativeCurrencyAmo
assertThat(subject.getRewardedAdCurrencyAmount()).isEqualTo(0);
}
+ @Test
+ public void loadWithSdkInitialized_withAdUnitId_shouldSetAdNetworkId() throws Exception {
+ final Map localExtras = new HashMap();
+ localExtras.put(DataKeys.AD_UNIT_ID_KEY, "adUnit");
+
+ subject.loadWithSdkInitialized(activity, localExtras, new HashMap());
+
+ assertThat(subject.getAdNetworkId()).isEqualTo("adUnit");
+ }
+
+ @Test
+ public void loadWithSdkInitialized_withNoAdUnitId_shouldUseDefaultAdNetworkId() throws Exception {
+ subject.loadWithSdkInitialized(activity, new HashMap(),
+ new HashMap());
+
+ assertThat(subject.getAdNetworkId()).isEqualTo(MoPubRewardedVideo.MOPUB_REWARDED_VIDEO_ID);
+ }
+
@Test
public void show_withVideoLoaded_shouldShowVastVideoInterstitial() {
subject.setRewardedVastVideoInterstitial(mockRewardedVastVideoInterstitial);
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubViewTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubViewTest.java
index 565920c55..71e4b64f8 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubViewTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/MoPubViewTest.java
@@ -11,7 +11,6 @@
import com.mopub.mobileads.test.support.TestAdViewControllerFactory;
import com.mopub.mobileads.test.support.TestCustomEventBannerAdapterFactory;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTaskTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTaskTest.java
index 3a26eed3c..eeaace597 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTaskTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoBlurLastVideoFrameTaskTest.java
@@ -1,12 +1,9 @@
package com.mopub.mobileads;
-import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
-import android.os.Build;
import android.widget.ImageView;
-import com.mopub.TestSdkHelper;
import com.mopub.common.test.support.SdkTestRunner;
import org.junit.Before;
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewControllerTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewControllerTest.java
index 757c5c28d..461d72a47 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewControllerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewControllerTest.java
@@ -874,19 +874,6 @@ public void onTouch_whenCloseButtonNotVisible_shouldNotPingClickThroughTrackers(
assertThat(FakeHttp.httpRequestWasMade()).isFalse();
}
- @Test
- public void onTouch_withNullBaseVideoViewListener_andActionTouchUp_shouldReturnTrueAndNotBlowUp() throws Exception {
- subject = new VastVideoViewController((Activity) context, bundle, null,
- 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();
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewTest.java
index 877a26c94..30b8ccba7 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/VastVideoViewTest.java
@@ -15,7 +15,6 @@
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
-import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
diff --git a/mopub-sdk/src/test/java/com/mopub/mobileads/WebViewCacheServiceTest.java b/mopub-sdk/src/test/java/com/mopub/mobileads/WebViewCacheServiceTest.java
index 89651a0aa..02e3b6d99 100644
--- a/mopub-sdk/src/test/java/com/mopub/mobileads/WebViewCacheServiceTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mobileads/WebViewCacheServiceTest.java
@@ -1,6 +1,5 @@
package com.mopub.mobileads;
-import android.app.Activity;
import android.os.Handler;
import com.mopub.common.ExternalViewabilitySessionManager;
@@ -10,7 +9,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import java.util.Map;
diff --git a/mopub-sdk/src/test/java/com/mopub/mraid/MraidBannerTest.java b/mopub-sdk/src/test/java/com/mopub/mraid/MraidBannerTest.java
index 7a3ba220f..ff0c47be6 100644
--- a/mopub-sdk/src/test/java/com/mopub/mraid/MraidBannerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mraid/MraidBannerTest.java
@@ -110,6 +110,16 @@ public void bannerMraidListener_onClose_shouldNotifyBannerCollapsed() {
verify(mockBannerListener).onBannerCollapsed();
}
+ @Test
+ public void trackMpxAndThirdPartyImpressions_shouldFireJavascriptWebViewDidAppear() {
+ MraidListener mraidListener = captureMraidListener();
+ mraidListener.onLoaded(null);
+ verify(mockBannerListener).onBannerLoaded(any(View.class));
+
+ subject.trackMpxAndThirdPartyImpressions();
+ verify(mockMraidController).loadJavascript(eq("webviewDidAppear();"));
+ }
+
private MraidListener captureMraidListener() {
subject.loadBanner(context, mockBannerListener, localExtras, serverExtras);
ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(MraidListener.class);
diff --git a/mopub-sdk/src/test/java/com/mopub/mraid/MraidVideoViewControllerTest.java b/mopub-sdk/src/test/java/com/mopub/mraid/MraidVideoViewControllerTest.java
index b46389dea..ba9c96b04 100644
--- a/mopub-sdk/src/test/java/com/mopub/mraid/MraidVideoViewControllerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/mraid/MraidVideoViewControllerTest.java
@@ -132,10 +132,6 @@ public void onCompletionListener_shouldShowCloseButton() throws Exception {
assertThat(getCloseButton().getVisibility()).isEqualTo(VISIBLE);
}
- @Test
- public void onCompletionListener_withNullBaseVideoViewControllerListener_shouldNotCallOnFinish() throws Exception {
- }
-
@Test
public void onErrorListener_shouldReturnFalseAndNotCallBaseVideoControllerListenerOnFinish() throws Exception {
initializeSubject();
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/ImpressionTrackerTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/ImpressionTrackerTest.java
index 6df8b430c..0a0b0d71f 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/ImpressionTrackerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/ImpressionTrackerTest.java
@@ -56,8 +56,10 @@ public void setUp() {
when(impressionInterface.getImpressionMinPercentageViewed()).thenReturn(50);
when(impressionInterface.getImpressionMinTimeViewed()).thenReturn(1000);
+ when(impressionInterface.getImpressionMinVisiblePx()).thenReturn(null);
when(impressionInterface2.getImpressionMinPercentageViewed()).thenReturn(50);
when(impressionInterface2.getImpressionMinTimeViewed()).thenReturn(1000);
+ when(impressionInterface2.getImpressionMinVisiblePx()).thenReturn(null);
// XXX We need this to ensure that our SystemClock starts
ShadowSystemClock.uptimeMillis();
@@ -70,7 +72,7 @@ public void addView_shouldAddViewToTrackedViews_shouldAddViewToVisibilityTracker
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(impressionInterface);
verify(visibilityTracker).addView(view, impressionInterface
- .getImpressionMinPercentageViewed());
+ .getImpressionMinPercentageViewed(), null);
}
@Test
@@ -81,7 +83,8 @@ public void addView_withRecordedImpression_shouldNotAddView() {
assertThat(trackedViews).hasSize(0);
verify(visibilityTracker, never())
- .addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ .addView(view, impressionInterface.getImpressionMinPercentageViewed(),
+ null);
}
@Test
@@ -90,7 +93,8 @@ public void addView_withDifferentImpressionInterface_shouldRemoveFromPollingView
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(impressionInterface);
- verify(visibilityTracker).addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ verify(visibilityTracker).addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
pollingViews.put(view, timeStampWrapper);
@@ -100,7 +104,7 @@ public void addView_withDifferentImpressionInterface_shouldRemoveFromPollingView
assertThat(trackedViews.get(view)).isEqualTo(impressionInterface2);
assertThat(pollingViews).isEmpty();
verify(visibilityTracker, times(2))
- .addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ .addView(view, impressionInterface.getImpressionMinPercentageViewed(), null);
}
@Test
@@ -111,7 +115,8 @@ public void addView_withDifferentAlreadyImpressedImpressionInterface_shouldRemov
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(impressionInterface);
- verify(visibilityTracker).addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ verify(visibilityTracker).addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
pollingViews.put(view, timeStampWrapper);
@@ -120,7 +125,8 @@ public void addView_withDifferentAlreadyImpressedImpressionInterface_shouldRemov
assertThat(trackedViews).hasSize(0);
assertThat(trackedViews.get(view)).isNull();
assertThat(pollingViews).isEmpty();
- verify(visibilityTracker).addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ verify(visibilityTracker).addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
}
@Test
@@ -129,7 +135,8 @@ public void addView_withSameImpressionInterface_shouldNotAddView() {
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(impressionInterface);
- verify(visibilityTracker).addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ verify(visibilityTracker).addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
pollingViews.put(view, timeStampWrapper);
@@ -140,14 +147,16 @@ public void addView_withSameImpressionInterface_shouldNotAddView() {
assertThat(pollingViews.keySet()).containsOnly(view);
// Still only one call
- verify(visibilityTracker).addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ verify(visibilityTracker).addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
}
@Test
public void removeView_shouldRemoveViewFromViewTrackedViews_shouldRemoveViewFromPollingMap_shouldRemoveViewFromVisibilityTracker() {
trackedViews.put(view, impressionInterface);
pollingViews.put(view, new TimestampWrapper(impressionInterface));
- visibilityTracker.addView(view, impressionInterface.getImpressionMinPercentageViewed());
+ visibilityTracker.addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
subject.removeView(view);
@@ -162,8 +171,10 @@ public void clear_shouldClearViewTrackedViews_shouldClearPollingViews_shouldClea
trackedViews.put(view2, impressionInterface);
pollingViews.put(view, timeStampWrapper);
pollingViews.put(view2, timeStampWrapper);
- visibilityTracker.addView(view, impressionInterface.getImpressionMinPercentageViewed());
- visibilityTracker.addView(view2, impressionInterface.getImpressionMinPercentageViewed());
+ visibilityTracker.addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
+ visibilityTracker.addView(view2,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
subject.clear();
@@ -179,8 +190,10 @@ public void destroy_shouldCallClear_shouldDestroyVisibilityTracker_shouldSetVisi
trackedViews.put(view2, impressionInterface);
pollingViews.put(view, timeStampWrapper);
pollingViews.put(view2, timeStampWrapper);
- visibilityTracker.addView(view, impressionInterface.getImpressionMinPercentageViewed());
- visibilityTracker.addView(view2, impressionInterface.getImpressionMinPercentageViewed());
+ visibilityTracker.addView(view,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
+ visibilityTracker.addView(view2,
+ impressionInterface.getImpressionMinPercentageViewed(), null);
assertThat(subject.getVisibilityTrackerListener()).isNotNull();
subject.destroy();
@@ -293,4 +306,4 @@ public void pollingRunnableRun_whenImpressionInterfaceIsNull_shouldThrowNPE() {
verify(impressionInterface, never()).recordImpression(view);
}
-}
\ No newline at end of file
+}
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubAdAdapterTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubAdAdapterTest.java
index 7e91e7dbf..669006f8c 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubAdAdapterTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubAdAdapterTest.java
@@ -18,6 +18,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -331,7 +332,8 @@ public void getView_withAdPosition_shouldReturnAdView_shouldTrackVisibility() {
assertThat(view).isEqualTo(mockAdView);
- verify(mockVisibilityTracker).addView(eq(mockAdView), anyInt());
+ verify(mockVisibilityTracker).addView(eq(mockAdView), anyInt(),
+ Matchers.isNull(Integer.class));
}
@Test
@@ -340,7 +342,8 @@ public void getView_withNonAdPosition_shouldOriginalAdapterView_shouldTrackVisib
assertThat(view).isNotEqualTo(mockAdView);
- verify(mockVisibilityTracker).addView(any(View.class), anyInt());
+ verify(mockVisibilityTracker).addView(any(View.class), anyInt(),
+ Matchers.isNull(Integer.class));
}
@Test
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubStaticNativeAdTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubStaticNativeAdTest.java
index 63b43fc00..eb1455754 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubStaticNativeAdTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubStaticNativeAdTest.java
@@ -18,7 +18,6 @@
import com.mopub.volley.toolbox.ImageLoader;
import org.json.JSONArray;
-import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubVideoNativeAdTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubVideoNativeAdTest.java
index bc9f21298..f373be7b2 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubVideoNativeAdTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/MoPubVideoNativeAdTest.java
@@ -353,7 +353,7 @@ public void render_shouldAddViewToVisibilityTracker() {
subject.prepare(mockRootView);
subject.render(mockMediaLayout);
- verify(mockVisibilityTracker).addView(mockRootView, mockMediaLayout, 10, 5);
+ verify(mockVisibilityTracker).addView(mockRootView, mockMediaLayout, 10, 5, null);
}
@Test
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/NativeVideoControllerTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/NativeVideoControllerTest.java
index 76187ea08..a3ef9e698 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/NativeVideoControllerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/NativeVideoControllerTest.java
@@ -17,16 +17,16 @@
import com.google.android.exoplayer2.ExoPlayer.ExoPlayerMessage;
import com.google.android.exoplayer2.LoadControl;
import com.google.android.exoplayer2.Renderer;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
+import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.TrackSelector;
+import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
import com.mopub.common.test.support.SdkTestRunner;
import com.mopub.mobileads.BuildConfig;
import com.mopub.mobileads.VastTracker;
import com.mopub.mobileads.VastVideoConfig;
-import com.mopub.nativeads.NativeVideoController.MoPubExoPlayerFactory;
import com.mopub.nativeads.NativeVideoController.Listener;
+import com.mopub.nativeads.NativeVideoController.MoPubExoPlayerFactory;
import com.mopub.nativeads.NativeVideoController.NativeVideoProgressRunnable;
import com.mopub.nativeads.NativeVideoController.NativeVideoProgressRunnable.ProgressListener;
import com.mopub.nativeads.NativeVideoController.VisibilityTrackingEvent;
@@ -39,6 +39,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
@@ -538,8 +539,10 @@ public void NativeVideoProgressRunnable_doWork_shouldTrackEventsWithMinimumPerce
when(mockExoPlayer.getCurrentPosition()).thenReturn(10L);
when(mockExoPlayer.getDuration()).thenReturn(25L);
when(mockExoPlayer.getPlayWhenReady()).thenReturn(true);
- when(mockVisibilityChecker.isVisible(mockTextureView, mockTextureView, 10)).thenReturn(true);
- when(mockVisibilityChecker.isVisible(mockTextureView, mockTextureView, 20)).thenReturn(false);
+ when(mockVisibilityChecker.isVisible(mockTextureView, mockTextureView,
+ 10, null)).thenReturn(true);
+ when(mockVisibilityChecker.isVisible(mockTextureView, mockTextureView,
+ 20, null)).thenReturn(false);
nativeVideoProgressRunnable.setUpdateIntervalMillis(10);
nativeVideoProgressRunnable.doWork();
@@ -639,7 +642,8 @@ public void NativeVideoProgressRunnable_doWork_withExoPlayerGetPlayWhenReadyFals
public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTriggerFalse_shouldOnlyTriggerNotTrackedEvents_shouldNotStopRunnable() {
when(mockExoPlayer.getCurrentPosition()).thenReturn(50L);
when(mockExoPlayer.getDuration()).thenReturn(50L);
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt()))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt(),
+ Matchers.isNull(Integer.class)))
.thenReturn(true);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(50);
@@ -659,7 +663,8 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
// Enough time has passed for all impressions to trigger organically
when(mockExoPlayer.getCurrentPosition()).thenReturn(50L);
when(mockExoPlayer.getDuration()).thenReturn(50L);
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt()))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt(),
+ Matchers.isNull(Integer.class)))
.thenReturn(true);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(50);
spyNativeVideoProgressRunnable.requestStop();
@@ -681,7 +686,8 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
// be triggered because forceTrigger is true
when(mockExoPlayer.getCurrentPosition()).thenReturn(5L);
when(mockExoPlayer.getDuration()).thenReturn(50L);
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt()))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt(),
+ Matchers.isNull(Integer.class)))
.thenReturn(true);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(50);
@@ -700,7 +706,8 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTriggerTrue_withStopRequested_shouldOnlyTriggerNotTrackedEvents_shouldStopRunnable() {
when(mockExoPlayer.getCurrentPosition()).thenReturn(50L);
when(mockExoPlayer.getDuration()).thenReturn(50L);
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt()))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), anyInt(),
+ Matchers.isNull(Integer.class)))
.thenReturn(true);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(50);
spyNativeVideoProgressRunnable.requestStop();
@@ -726,16 +733,20 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
// track: whether the impression should be organically triggered
// trackingUrl1: visible & played = track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(10)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(10), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl2: visible & !played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(20)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(20), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl3: already tracked = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(30)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(30), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl4: !visible & played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(9)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(9), Matchers.isNull(Integer.class)))
.thenReturn(false);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(10);
spyNativeVideoProgressRunnable.requestStop();
@@ -763,16 +774,20 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
// track: whether the impression should be organically triggered
// trackingUrl1: visible & played = track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(10)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(10), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl2: visible & !played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(20)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(20), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl3: already tracked = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(30)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(30), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl4: !visible & played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(9)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(9), Matchers.isNull(Integer.class)))
.thenReturn(false);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(10);
@@ -800,16 +815,20 @@ public void NativeVideoProgressRunnable_checkImpressionTrackers_withForceTrigger
// track: whether the impression should be organically triggered
// trackingUrl1: visible & played = track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(10)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(10), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl2: visible & !played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(20)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(20), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl3: already tracked = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(30)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(30), Matchers.isNull(Integer.class)))
.thenReturn(true);
// trackingUrl4: !visible & played = !track
- when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView), eq(9)))
+ when(mockVisibilityChecker.isVisible(eq(mockTextureView), eq(mockTextureView),
+ eq(9), Matchers.isNull(Integer.class)))
.thenReturn(false);
spyNativeVideoProgressRunnable.setUpdateIntervalMillis(10);
spyNativeVideoProgressRunnable.requestStop();
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/VisibilityTrackerTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/VisibilityTrackerTest.java
index 2c6be2a78..4a903940d 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/VisibilityTrackerTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/VisibilityTrackerTest.java
@@ -47,6 +47,7 @@
@Config(constants = BuildConfig.class)
public class VisibilityTrackerTest {
private static final int MIN_PERCENTAGE_VIEWED = 50;
+ private static final Integer DEFAULT_MIN_VISIBLE_PX = 1;
private Activity activity;
private VisibilityTracker subject;
@@ -128,7 +129,7 @@ public void constructor_withApplicationContext_shouldNotSetOnPreDrawListener() {
@Test
public void addView_withVisibleView_shouldAddVisibleViewToTrackedViews() throws Exception {
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(1);
}
@@ -145,21 +146,21 @@ public void addView_withViewTreeObserverNotSet_shouldSetViewTreeObserver() {
subject = new VisibilityTracker(activity.getApplicationContext(), trackedViews,
visibilityChecker, visibilityHandler);
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
assertThat(subject.mWeakViewTreeObserver.get()).isEqualTo(viewTreeObserver);
}
@Test(expected = NullPointerException.class)
public void addView_whenViewIsNull_shouldThrowNPE() throws Exception {
- subject.addView(null, MIN_PERCENTAGE_VIEWED);
+ subject.addView(null, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).isEmpty();
}
@Test
public void removeView_shouldRemoveFromTrackedViews() throws Exception {
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews).containsKey(view);
@@ -171,8 +172,8 @@ public void removeView_shouldRemoveFromTrackedViews() throws Exception {
@Test
public void clear_shouldRemoveAllViewsFromTrackedViews_shouldRemoveMessagesFromVisibilityHandler_shouldResetIsVisibilityScheduled() throws Exception {
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(2);
subject.clear();
@@ -196,8 +197,8 @@ public void destroy_shouldCallClear_shouldRemoveListenerFromDecorView() throws E
subject = new VisibilityTracker(activity1, trackedViews, visibilityChecker, visibilityHandler);
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(2);
subject.destroy();
@@ -210,7 +211,7 @@ public void destroy_shouldCallClear_shouldRemoveListenerFromDecorView() throws E
@Test
public void visibilityRunnable_run_withVisibleView_shouldCallOnVisibleCallback() throws Exception {
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
subject.new VisibilityRunnable().run();
@@ -221,7 +222,7 @@ public void visibilityRunnable_run_withVisibleView_shouldCallOnVisibleCallback()
@Test
public void visibilityRunnable_run_withNonVisibleView_shouldCallOnNonVisibleCallback() throws Exception {
when(view.getVisibility()).thenReturn(View.INVISIBLE);
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
subject.new VisibilityRunnable().run();
@@ -249,89 +250,121 @@ public void hasRequiredTimeElapsed_withElapsedTimeLessThanMinTimeViewed_shouldRe
@Test
public void isMostlyVisible_whenParentIsNull_shouldReturnFalse() throws Exception {
view = createViewMock(View.VISIBLE, 100, 100, 100, 100, false, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenViewIsOffScreen_shouldReturnFalse() throws Exception {
view = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, false);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenViewIsEntirelyOnScreen_shouldReturnTrue() throws Exception {
view = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isTrue();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isTrue();
}
@Test
public void isMostlyVisible_whenViewIs50PercentVisible_shouldReturnTrue() throws Exception {
view = createViewMock(View.VISIBLE, 50, 100, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isTrue();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isTrue();
}
@Test
public void isMostlyVisible_whenViewIs49PercentVisible_shouldReturnFalse() throws Exception {
view = createViewMock(View.VISIBLE, 49, 100, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenVisibleAreaIsZero_shouldReturnFalse() throws Exception {
view = createViewMock(View.VISIBLE, 0, 0, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenViewIsInvisibleOrGone_shouldReturnFalse() throws Exception {
View view = createViewMock(View.INVISIBLE, 100, 100, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
reset(view);
view = createViewMock(View.GONE, 100, 100, 100, 100, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenViewHasZeroWidthAndHeight_shouldReturnFalse() throws Exception {
view = createViewMock(View.VISIBLE, 100, 100, 0, 0, true, true);
- assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED, null)).isFalse();
}
@Test
public void isMostlyVisible_whenViewIsNull_shouldReturnFalse() throws Exception {
- assertThat(visibilityChecker.isVisible(null, null, MIN_PERCENTAGE_VIEWED)).isFalse();
+ assertThat(visibilityChecker.isVisible(null, null, MIN_PERCENTAGE_VIEWED, null)).isFalse();
+ }
+
+ @Test
+ public void isMostlyVisible_whenVisibleAreaIsCheckedByPixel_shouldReturnTrue() throws Exception {
+ view = createViewMock(View.VISIBLE, 90, 90, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED,
+ DEFAULT_MIN_VISIBLE_PX)).isTrue();
+ }
+
+ @Test
+ public void isVisible_whenVisibleAreaIsCheckedByPixel_withExactlyOnePixelVisible_shouldReturnTrue() throws Exception {
+ view = createViewMock(View.VISIBLE, 1, 1, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED,
+ DEFAULT_MIN_VISIBLE_PX)).isTrue();
+ }
+
+ @Test
+ public void isVisible_whenVisibleAreaIsCheckedByPixel_withLargeNonDefaultMinimumPixel_shouldReturnFalse() throws Exception {
+ view = createViewMock(View.VISIBLE, 3, 3, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED,
+ 25)).isFalse();
+ }
+
+ @Test
+ public void isVisible_whenVisibleAreaIsCheckedByPixel_withSmallNonDefaultMinimumPixel_shouldReturnTrue() throws Exception {
+ view = createViewMock(View.VISIBLE, 3, 3, 100, 100, true, true);
+
+ assertThat(visibilityChecker.isVisible(view, view, MIN_PERCENTAGE_VIEWED,
+ 5)).isTrue();
}
@Test
public void addView_shouldClearViewAfterNumAccesses() {
// Access 1 time
- subject.addView(view, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(1);
// Access 2-49 times
for (int i = 0; i < VisibilityTracker.NUM_ACCESSES_BEFORE_TRIMMING - 2; ++i) {
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
}
assertThat(trackedViews).hasSize(2);
// 50th time
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(2);
// 51-99
for (int i = 0; i < VisibilityTracker.NUM_ACCESSES_BEFORE_TRIMMING - 1; ++i) {
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
}
assertThat(trackedViews).hasSize(2);
// 100
- subject.addView(view2, MIN_PERCENTAGE_VIEWED);
+ subject.addView(view2, MIN_PERCENTAGE_VIEWED, null);
assertThat(trackedViews).hasSize(1);
}
diff --git a/mopub-sdk/src/test/java/com/mopub/nativeads/factories/CustomEventNativeFactoryTest.java b/mopub-sdk/src/test/java/com/mopub/nativeads/factories/CustomEventNativeFactoryTest.java
index f13a663f0..5db56a217 100644
--- a/mopub-sdk/src/test/java/com/mopub/nativeads/factories/CustomEventNativeFactoryTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/nativeads/factories/CustomEventNativeFactoryTest.java
@@ -7,13 +7,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
-
@Config(constants = BuildConfig.class)
@RunWith(SdkTestRunner.class)
public class CustomEventNativeFactoryTest {
diff --git a/mopub-sdk/src/test/java/com/mopub/network/AdRequestTest.java b/mopub-sdk/src/test/java/com/mopub/network/AdRequestTest.java
index 2d5a13f1c..094878e14 100644
--- a/mopub-sdk/src/test/java/com/mopub/network/AdRequestTest.java
+++ b/mopub-sdk/src/test/java/com/mopub/network/AdRequestTest.java
@@ -71,6 +71,7 @@ public void setup() {
defaultHeaders.put(ResponseHeader.PAUSE_VISIBLE_PERCENT.getKey(), "25");
defaultHeaders.put(ResponseHeader.IMPRESSION_MIN_VISIBLE_PERCENT.getKey(), "33%");
defaultHeaders.put(ResponseHeader.IMPRESSION_VISIBLE_MS.getKey(), "2000");
+ defaultHeaders.put(ResponseHeader.IMPRESSION_MIN_VISIBLE_PX.getKey(), "1");
defaultHeaders.put(ResponseHeader.MAX_BUFFER_MS.getKey(), "1000");
MoPubEvents.setEventDispatcher(mockEventDispatcher);
@@ -169,6 +170,7 @@ public void parseNetworkResponse_forNativeVideo_shouldSucceed() throws Exception
assertThat(serverExtras.get(DataKeys.PAUSE_VISIBLE_PERCENT)).isEqualTo("25");
assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT)).isEqualTo("33");
assertThat(serverExtras.get(DataKeys.IMPRESSION_VISIBLE_MS)).isEqualTo("2000");
+ assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX)).isEqualTo("1");
assertThat(serverExtras.get(DataKeys.MAX_BUFFER_MS)).isEqualTo("1000");
}
@@ -186,6 +188,7 @@ public void parseNetworkResponse_forNativeStatic_shouldSucceed() throws Exceptio
assertThat(serverExtras).isNotEmpty();
assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT)).isEqualTo("33");
assertThat(serverExtras.get(DataKeys.IMPRESSION_VISIBLE_MS)).isEqualTo("2000");
+ assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX)).isEqualTo("1");
}
@Test
@@ -208,6 +211,7 @@ public void parseNetworkResponse_forNativeVideo_shouldCombineServerExtrasAndEven
assertThat(serverExtras.get(DataKeys.PAUSE_VISIBLE_PERCENT)).isEqualTo("25");
assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT)).isEqualTo("33");
assertThat(serverExtras.get(DataKeys.IMPRESSION_VISIBLE_MS)).isEqualTo("2000");
+ assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX)).isEqualTo("1");
assertThat(serverExtras.get(DataKeys.MAX_BUFFER_MS)).isEqualTo("1000");
assertThat(serverExtras.get("customEventKey1")).isEqualTo("value1");
@@ -219,6 +223,7 @@ public void parseNetworkResponse_forNativeVideo_withInvalidValues_shouldSucceed_
defaultHeaders.put(ResponseHeader.AD_TYPE.getKey(), AdType.VIDEO_NATIVE);
defaultHeaders.put(ResponseHeader.PLAY_VISIBLE_PERCENT.getKey(), "-1");
defaultHeaders.put(ResponseHeader.PAUSE_VISIBLE_PERCENT.getKey(), "101%");
+ defaultHeaders.put(ResponseHeader.IMPRESSION_MIN_VISIBLE_PX.getKey(), "bob");
defaultHeaders.put(ResponseHeader.IMPRESSION_MIN_VISIBLE_PERCENT.getKey(), "XX%");
NetworkResponse testResponse = new NetworkResponse(200,
"{\"abc\": \"def\"}".getBytes(Charset.defaultCharset()), defaultHeaders, false);
@@ -233,6 +238,7 @@ public void parseNetworkResponse_forNativeVideo_withInvalidValues_shouldSucceed_
assertThat(serverExtras.get(DataKeys.PAUSE_VISIBLE_PERCENT)).isNull();
assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PERCENT)).isNull();
assertThat(serverExtras.get(DataKeys.IMPRESSION_VISIBLE_MS)).isEqualTo("2000");
+ assertThat(serverExtras.get(DataKeys.IMPRESSION_MIN_VISIBLE_PX)).isEqualTo("bob");
assertThat(serverExtras.get(DataKeys.MAX_BUFFER_MS)).isEqualTo("1000");
}
@@ -415,6 +421,73 @@ public void parseNetworkResponse_withUndefinedBrowserAgent_shouldDefaultToInApp(
assertThat(response.result.getBrowserAgent()).isEqualTo(BrowserAgent.IN_APP);
}
+ @Test
+ public void parseNetworkResponse_forBannerAdFormat_withoutImpTrackingHeaders_shouldSucceed() {
+ subject = new AdRequest("testUrl", AdFormat.BANNER, "testAdUnitId", activity, mockListener);
+
+ NetworkResponse testResponse =
+ new NetworkResponse(200, "abc".getBytes(Charset.defaultCharset()), defaultHeaders, false);
+
+ final Response response = subject.parseNetworkResponse(testResponse);
+
+ assertThat(response.result).isNotNull();
+ assertThat(response.result.getStringBody()).isEqualTo("abc");
+
+ // Check the server extras
+ final Map serverExtras = response.result.getServerExtras();
+ assertThat(serverExtras).isNotNull();
+ assertThat(serverExtras).isNotEmpty();
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS)).isNull();
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS)).isNull();
+ }
+
+ @Test
+ public void parseNetworkResponse_forBannerAdFormat_withImpTrackingHeaders_shouldSucceed_shouldStoreHeadersInServerExtras() {
+ defaultHeaders.put(ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_DIPS.getKey(), "1");
+ defaultHeaders.put(ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_MS.getKey(), "0");
+
+ subject = new AdRequest("testUrl", AdFormat.BANNER, "testAdUnitId", activity, mockListener);
+
+ NetworkResponse testResponse =
+ new NetworkResponse(200, "abc".getBytes(Charset.defaultCharset()), defaultHeaders, false);
+
+ final Response response = subject.parseNetworkResponse(testResponse);
+
+ assertThat(response.result).isNotNull();
+ assertThat(response.result.getStringBody()).isEqualTo("abc");
+
+ // Check the server extras
+ final Map serverExtras = response.result.getServerExtras();
+ assertThat(serverExtras).isNotNull();
+ assertThat(serverExtras).isNotEmpty();
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS)).isEqualTo("1");
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS)).isEqualTo("0");
+ }
+
+ @Test
+ public void parseNetworkResponse_forNonBannerAdFormat_withImpTrackingHeaders_shouldSucceed_shouldIgnoreHeaders() {
+ defaultHeaders.put(ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_DIPS.getKey(), "1");
+ defaultHeaders.put(ResponseHeader.BANNER_IMPRESSION_MIN_VISIBLE_MS.getKey(), "0");
+
+ // Non-banner AdFormat
+ subject = new AdRequest("testUrl", AdFormat.INTERSTITIAL, "testAdUnitId", activity, mockListener);
+
+ NetworkResponse testResponse =
+ new NetworkResponse(200, "abc".getBytes(Charset.defaultCharset()), defaultHeaders, false);
+
+ final Response response = subject.parseNetworkResponse(testResponse);
+
+ assertThat(response.result).isNotNull();
+ assertThat(response.result.getStringBody()).isEqualTo("abc");
+
+ // Check the server extras
+ final Map serverExtras = response.result.getServerExtras();
+ assertThat(serverExtras).isNotNull();
+ assertThat(serverExtras).isNotEmpty();
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_DIPS)).isNull();
+ assertThat(serverExtras.get(DataKeys.BANNER_IMPRESSION_MIN_VISIBLE_MS)).isNull();
+ }
+
@Test
public void deliverResponse_shouldCallListenerOnSuccess() throws Exception {
subject.deliverResponse(mockAdResponse);