diff --git a/AMZNMediaPlayerComponent/build.gradle b/AMZNMediaPlayerComponent/build.gradle index d3655cc..33dbc31 100644 --- a/AMZNMediaPlayerComponent/build.gradle +++ b/AMZNMediaPlayerComponent/build.gradle @@ -50,7 +50,6 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.1.1' compile files('libs/AMZNMediaPlayer.jar') - compile files('libs/exoplayer.jar') compile project(':ModuleInterface') compile project(':UAMP') diff --git a/AMZNMediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/AMZNPlayer.java b/AMZNMediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/AMZNPlayer.java index 7173782..bfb27a6 100644 --- a/AMZNMediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/AMZNPlayer.java +++ b/AMZNMediaPlayerComponent/src/main/java/com/amazon/mediaplayer/glue/AMZNPlayer.java @@ -379,38 +379,39 @@ public void enableTextTrack(TrackType trackType, boolean b) { } /* - * TODO: Not implemented yet DEVTECH-2280 + * {@inheritDoc} */ @Override public int getTrackCount(TrackType trackType) { - return 0; + return mPlayer.getTrackCount(trackType); } /* - * TODO: Not implemented yet DEVTECH-2280 + * {@inheritDoc} */ @Override public MediaFormat getTrackFormat(TrackType trackType, int i) { - return null; + return mPlayer.getTrackFormat(trackType, i); } /* - * TODO: Not implemented yet DEVTECH-2280 + * {@inheritDoc} */ @Override public void setSelectedTrack(TrackType trackType, int i) { + mPlayer.setSelectedTrack(trackType, i); } /* - * TODO: Not implemented yet DEVTECH-2280 + * {@inheritDoc} */ @Override public int getSelectedTrack(TrackType trackType) { - return 0; + return mPlayer.getSelectedTrack(trackType); } /* diff --git a/AdobepassAuthComponent/src/main/java/com/amazon/adobepass/auth/AdobepassAuthentication.java b/AdobepassAuthComponent/src/main/java/com/amazon/adobepass/auth/AdobepassAuthentication.java index a6147ac..7e88abc 100644 --- a/AdobepassAuthComponent/src/main/java/com/amazon/adobepass/auth/AdobepassAuthentication.java +++ b/AdobepassAuthComponent/src/main/java/com/amazon/adobepass/auth/AdobepassAuthentication.java @@ -126,8 +126,7 @@ public void onFailure(int statusCode, Header[] headers, String }); } else { - bundle.putString(AuthenticationConstants.ERROR_CATEGORY, - AuthenticationConstants.NETWORK_ERROR_CATEGORY); + populateErrorBundle(bundle, AuthenticationConstants.NETWORK_ERROR_CATEGORY); responseHandler.onFailure(bundle); } } @@ -201,8 +200,7 @@ public void onFailure(int statusCode, Header[] headers, String }); } else { - bundle.putString(AuthenticationConstants.ERROR_CATEGORY, - AuthenticationConstants.NETWORK_ERROR_CATEGORY); + populateErrorBundle(bundle, AuthenticationConstants.NETWORK_ERROR_CATEGORY); responseHandler.onFailure(bundle); } @@ -267,8 +265,7 @@ public void onFailure(int statusCode, Header[] headers, String }); } else { - bundle.putString(AuthenticationConstants.ERROR_CATEGORY, - AuthenticationConstants.NETWORK_ERROR_CATEGORY); + populateErrorBundle(bundle, AuthenticationConstants.NETWORK_ERROR_CATEGORY); responseHandler.onFailure(bundle); } } @@ -292,12 +289,15 @@ public void onFailure(int statusCode, Header[] headers, String private void populateAuthorizationFailureBundle(int statusCode, Bundle bundle, Throwable throwable) { - bundle.putInt(ResponseHandler.STATUS_CODE, statusCode); - bundle.putString( + Bundle errorBundle = new Bundle(); + errorBundle.putInt(ResponseHandler.STATUS_CODE, statusCode); + errorBundle.putString( AuthenticationConstants.ERROR_CATEGORY, AuthenticationConstants.AUTHORIZATION_ERROR_CATEGORY); - bundle.putSerializable( + errorBundle.putSerializable( AuthenticationConstants.ERROR_CAUSE, throwable); + bundle.putBundle( + AuthenticationConstants.ERROR_BUNDLE, errorBundle); } /** @@ -318,11 +318,30 @@ public void cancelAllRequests() { private void populateAuthenticationFailureBundle(int statusCode, Bundle bundle, Throwable throwable) { - bundle.putInt(ResponseHandler.STATUS_CODE, statusCode); - bundle.putString( + Bundle errorBundle = new Bundle(); + errorBundle.putInt(ResponseHandler.STATUS_CODE, statusCode); + errorBundle.putString( AuthenticationConstants.ERROR_CATEGORY, AuthenticationConstants.AUTHENTICATION_ERROR_CATEGORY); - bundle.putSerializable( + errorBundle.putSerializable( AuthenticationConstants.ERROR_CAUSE, throwable); + bundle.putBundle( + AuthenticationConstants.ERROR_BUNDLE, errorBundle); + } + + /** + * Bundle to be sent on failures other than Authentication and Authorization + * + * @param bundle Bundle to populate + * @param errorCategory Error Category + */ + private void populateErrorBundle(Bundle bundle, String errorCategory) { + + Bundle errorBundle = new Bundle(); + errorBundle.putString( + AuthenticationConstants.ERROR_CATEGORY, + errorCategory); + bundle.putBundle( + AuthenticationConstants.ERROR_BUNDLE, errorBundle); } } diff --git a/AdsInterface/src/main/java/com/amazon/ads/AdMetaData.java b/AdsInterface/src/main/java/com/amazon/ads/AdMetaData.java new file mode 100644 index 0000000..60ca0ca --- /dev/null +++ b/AdsInterface/src/main/java/com/amazon/ads/AdMetaData.java @@ -0,0 +1,132 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.ads; + +/** + * Class to hold Ad metadata. + */ +public class AdMetaData { + + /** + * Ad ID. + */ + private String adId = ""; + + /** + * Duration of ad played on screen. + */ + private long durationPlayed; + + /** + * Duration of ad received in ad meta data. + */ + private long durationReceived; + + /** + * Ad Type: Pre roll, Mid roll, Post roll + */ + private String adType = ""; + + /** + * set id of current ad. + * + * @param adId current ad id received in ad metadata. + */ + public void setAdId(String adId) { + + this.adId = adId; + } + + /** + * set duration of current ad played on screen. + * + * @param durationPlayed duration of ad played on screen + */ + public void setDurationPlayed(long durationPlayed) { + + this.durationPlayed = durationPlayed; + } + + /** + * set duration of ad received in ad meta data. + * + * @param durationReceived duration of ad received in ad meta data + */ + public void setDurationReceived(long durationReceived) { + + this.durationReceived = durationReceived; + } + + /** + * set ad type in ad meta data. + * + * @param adType pre, mid or post roll + */ + public void setAdType(String adType) { + + this.adType = adType; + } + + /** + * Return the ad Id. + * + * @return id of current ad. + */ + public String getAdId() { + + return adId; + } + + /** + * Return the duration played of current ad. + * + * @return duration of ad played on screen. + */ + public long getDurationPlayed() { + + return durationPlayed; + } + + /** + * Return the duration received from ad module. + * + * @return duration of ad as received from ad meta data. + */ + public long getDurationReceived() { + + return durationReceived; + } + + /** + * Return the ad type. + * + * @return type of current ad. + */ + public String getAdType() { + + return adType; + } + + @Override + public String toString() { + + return "AdMetaData{" + + "adId='" + adId + '\'' + + ", durationPlayed=" + durationPlayed + + ", durationReceived=" + durationReceived + + ", adType='" + adType + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/AdsInterface/src/main/java/com/amazon/ads/IAds.java b/AdsInterface/src/main/java/com/amazon/ads/IAds.java index ec14274..bf5fa7a 100644 --- a/AdsInterface/src/main/java/com/amazon/ads/IAds.java +++ b/AdsInterface/src/main/java/com/amazon/ads/IAds.java @@ -24,14 +24,50 @@ public interface IAds { /** - * Constant for duration field. + * Constant for Ad Id. */ - String DURATION = "duration"; + String ID = "id"; /** - * Constant for wasAMidRoll field. + * Constant for duration received from ad metadata. */ - String WAS_A_MID_ROLL = "wasAMidRoll"; + String DURATION_RECEIVED = "durationReceived"; + + /** + * Constant for duration calculated during ad play. + */ + String DURATION_PLAYED = "durationPlayed"; + + /** + * Constant for getting the ad pod complete boolean out of the extras bundle. + */ + String AD_POD_COMPLETE = "adPodComplete"; + + /** + * Constant for getting the ad type out of the extras bundle. + */ + String AD_TYPE = "ad_type"; + + /** + * Constant for a pre-roll ad. + */ + String PRE_ROLL_AD = "preroll"; + + /** + * Constant for a mid-roll ad. + */ + String MID_ROLL_AD = "midroll"; + + /** + * Constant for a post-roll ad. + */ + String POST_ROLL_AD = "postroll"; + + /** + * Parameter to add to an ad tag URL with a timestamp so the add will play consecutively if + * called upon. + */ + String CORRELATOR_PARAMETER = "correlator"; /** * Major version number. @@ -46,8 +82,8 @@ public interface IAds { /** * Init Ads instance. * - * @param context Context which Ads consumed in. - * @param frameLayout Layout for Ads. + * @param context The context. + * @param frameLayout Layout for the Ads player. * @param extras Extra bundle to pass through data. */ void init(Context context, FrameLayout frameLayout, Bundle extras); @@ -91,6 +127,13 @@ interface IAdsEvents { */ void setCurrentVideoPosition(double position); + /** + * Return true if there are one or more post roll ads to play; false otherwise. + * + * @return True if there are one or more post roll ads to play; false otherwise. + */ + boolean isPostRollAvailable(); + /** * Activity states for ads implementation consumption. */ diff --git a/AnalyticsInterface/src/main/java/com/amazon/analytics/AnalyticsTags.java b/AnalyticsInterface/src/main/java/com/amazon/analytics/AnalyticsTags.java index 9f98e8a..680cea5 100644 --- a/AnalyticsInterface/src/main/java/com/amazon/analytics/AnalyticsTags.java +++ b/AnalyticsInterface/src/main/java/com/amazon/analytics/AnalyticsTags.java @@ -179,6 +179,51 @@ public class AnalyticsTags { public static final String ACTION_UPDATE_RELATED_RECOMMENDATIONS = "ACTION_UPDATE_RELATED_RECOMMENDATIONS"; + /** + * Customizable action for user authentication requested. + */ + public static final String ACTION_AUTHENTICATION_REQUESTED = "ACTION_AUTHENTICATION_REQUESTED"; + + /** + * Customizable action for user authentication succeeded. + */ + public static final String ACTION_AUTHENTICATION_SUCCEEDED = "ACTION_AUTHENTICATION_SUCCEEDED"; + + /** + * Customizable action for user authentication failed. + */ + public static final String ACTION_AUTHENTICATION_FAILED = "ACTION_AUTHENTICATION_FAILED"; + + /** + * Customizable action for user Authorization requested. + */ + public static final String ACTION_AUTHORIZATION_REQUESTED = "ACTION_AUTHORIZATION_REQUESTED"; + + /** + * Customizable action for user Authorization succeeded. + */ + public static final String ACTION_AUTHORIZATION_SUCCEEDED = "ACTION_AUTHORIZATION_SUCCEEDED"; + + /** + * Customizable action for user Authorization failed. + */ + public static final String ACTION_AUTHORIZATION_FAILED = "ACTION_AUTHORIZATION_FAILED"; + + /** + * Customizable action for user log out requested. + */ + public static final String ACTION_LOG_OUT_REQUESTED = "ACTION_LOG_OUT_REQUESTED"; + + /** + * Customizable action for user log out succeeded. + */ + public static final String ACTION_LOG_OUT_SUCCEEDED = "ACTION_LOG_OUT_SUCCEEDED"; + + /** + * Customizable action for user log out failed. + */ + public static final String ACTION_LOG_OUT_FAILED = "ACTION_LOG_OUT_FAILED"; + ///////////////////////////////////////////////////////////////////////// // Customizable attributes. // ///////////////////////////////////////////////////////////////////////// @@ -304,6 +349,11 @@ public class AnalyticsTags { */ public static final String ATTRIBUTE_VIDEO_DURATION = "ATTRIBUTE_VIDEO_DURATION"; + /** + * Customizable attribute for the duration of the ad. + */ + public static final String ATTRIBUTE_AD_DURATION = "ATTRIBUTE_AD_DURATION"; + /** * Customizable attribute for the ID of the content. */ @@ -360,6 +410,78 @@ public class AnalyticsTags { */ public static final String ATTRIBUTE_RECOMMENDATION_ID = "ATTRIBUTE_RECOMMENDATION_ID"; + /** + * Customizable attribute for the Authentication submitted + */ + public static final String ATTRIBUTE_AUTHENTICATION_SUBMITTED = + "ATTRIBUTE_AUTHENTICATION_SUBMITTED"; + + /** + * Customizable attribute for the Authentication success + */ + public static final String ATTRIBUTE_AUTHENTICATION_SUCCESS = + "ATTRIBUTE_AUTHENTICATION_SUCCESS"; + + /** + * Customizable attribute for the Authentication failure + */ + public static final String ATTRIBUTE_AUTHENTICATION_FAILURE = + "ATTRIBUTE_AUTHENTICATION_FAILURE"; + + /** + * Customizable attribute for the Authentication failure reason + */ + public static final String ATTRIBUTE_AUTHENTICATION_FAILURE_REASON = + "ATTRIBUTE_AUTHENTICATION_FAILURE_REASON"; + + /** + * Customizable attribute for the Logout submitted + */ + public static final String ATTRIBUTE_LOGOUT_SUBMITTED = + "ATTRIBUTE_LOGOUT_SUBMITTED"; + + /** + * Customizable attribute for the Logout success + */ + public static final String ATTRIBUTE_LOGOUT_SUCCESS = + "ATTRIBUTE_LOGOUT_SUCCESS"; + + /** + * Customizable attribute for the Logout failure + */ + public static final String ATTRIBUTE_LOGOUT_FAILURE = + "ATTRIBUTE_LOGOUT_FAILURE"; + + /** + * Customizable attribute for the Logout Failure reason + */ + public static final String ATTRIBUTE_LOGOUT_FAILURE_REASON = + "ATTRIBUTE_LOGOUT_FAILURE_REASON"; + + /** + * Customizable attribute for the Authorization submitted + */ + public static final String ATTRIBUTE_AUTHORIZATION_SUBMITTED = + "ATTRIBUTE_AUTHORIZATION_SUBMITTED"; + + /** + * Customizable attribute for the Authorization success + */ + public static final String ATTRIBUTE_AUTHORIZATION_SUCCESS = + "ATTRIBUTE_AUTHORIZATION_SUCCESS"; + + /** + * Customizable attribute for the Authorization failure + */ + public static final String ATTRIBUTE_AUTHORIZATION_FAILURE = + "ATTRIBUTE_AUTHORIZATION_FAILURE"; + + /** + * Customizable attribute for the Authorization failure reason + */ + public static final String ATTRIBUTE_AUTHORIZATION_FAILURE_REASON = + "ATTRIBUTE_AUTHORIZATION_FAILURE_REASON"; + ///////////////////////////////////////////////////////////////////////// // Non-Customizable screen names. // ///////////////////////////////////////////////////////////////////////// @@ -408,4 +530,4 @@ public class AnalyticsTags { */ public static final String ATTRIBUTES = "attributes"; -} \ No newline at end of file +} diff --git a/AnalyticsInterface/src/main/java/com/amazon/analytics/CustomAnalyticsTags.java b/AnalyticsInterface/src/main/java/com/amazon/analytics/CustomAnalyticsTags.java index 7656ab7..0b370b8 100644 --- a/AnalyticsInterface/src/main/java/com/amazon/analytics/CustomAnalyticsTags.java +++ b/AnalyticsInterface/src/main/java/com/amazon/analytics/CustomAnalyticsTags.java @@ -18,7 +18,7 @@ import android.util.Log; import com.amazon.android.utils.FileHelper; -import com.amazon.android.utils.Helpers; +import com.amazon.android.utils.MapHelper; import java.util.HashMap; @@ -51,7 +51,7 @@ public void init(Context context, int fileNameId) { // Read the file only if it exists. if (FileHelper.doesFileExist(context, context.getResources().getString(fileNameId))) { - mCustomTags = Helpers.loadStringMappingFromJsonFile(context, fileNameId); + mCustomTags = MapHelper.loadStringMappingFromJsonFile(context, fileNameId); Log.d(TAG, "Custom analytics tags initialized"); } } diff --git a/AnalyticsInterface/src/main/java/com/amazon/analytics/ExtraContentAttributes.java b/AnalyticsInterface/src/main/java/com/amazon/analytics/ExtraContentAttributes.java index 599ecc1..7f1547a 100644 --- a/AnalyticsInterface/src/main/java/com/amazon/analytics/ExtraContentAttributes.java +++ b/AnalyticsInterface/src/main/java/com/amazon/analytics/ExtraContentAttributes.java @@ -17,7 +17,7 @@ import android.content.Context; import android.util.Log; -import com.amazon.android.utils.Helpers; +import com.amazon.android.utils.MapHelper; import java.util.HashMap; import java.util.Map; @@ -44,7 +44,7 @@ public class ExtraContentAttributes { */ public static void init(Context context) { - mAttributeToExtraMap = Helpers.loadStringMappingFromJsonFile(context, + mAttributeToExtraMap = MapHelper.loadStringMappingFromJsonFile(context, R.string.analytics_attribute_to_content_extra_map_file); Log.d(TAG, "Extra attribute mapping: " + mAttributeToExtraMap.toString()); } diff --git a/Application/app/build.gradle b/Application/app/build.gradle index 243bbf0..b157524 100644 --- a/Application/app/build.gradle +++ b/Application/app/build.gradle @@ -44,8 +44,8 @@ android { applicationId "com.amazon.android.calypso" minSdkVersion 21 targetSdkVersion 23 - versionCode 8 - versionName "1.0.4" + versionCode 9 + versionName "1.0.5" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } diff --git a/Application/app/src/main/AndroidManifest.xml b/Application/app/src/main/AndroidManifest.xml index 4e196e9..580a33b 100644 --- a/Application/app/src/main/AndroidManifest.xml +++ b/Application/app/src/main/AndroidManifest.xml @@ -25,7 +25,8 @@ permissions and limitations under the License. + android:noHistory="true" + android:launchMode="singleTask"> diff --git a/Application/app/src/main/assets/Navigator.json b/Application/app/src/main/assets/Navigator.json index ab505ab..1a153ff 100644 --- a/Application/app/src/main/assets/Navigator.json +++ b/Application/app/src/main/assets/Navigator.json @@ -2,7 +2,8 @@ "config": { "showRelatedContent": true, "useCategoryAsDefaultRelatedContent": true, - "searchAlgo": "basic" + "searchAlgo": "basic", + "enableCEA608": false }, "branding": { "globalTheme": "AppTheme", diff --git a/Application/settings.gradle b/Application/settings.gradle index d9084fc..4912b06 100644 --- a/Application/settings.gradle +++ b/Application/settings.gradle @@ -23,6 +23,8 @@ include ':app', ':DynamicParser', ':DataLoader', ':Utils', + // Uncomment when using ComScore Analytics + //':comscore', /* Interfaces */ ':PurchaseInterface', ':AuthInterface', @@ -59,3 +61,5 @@ project(':AMZNMediaPlayerComponent').projectDir = new File(rootProject.projectDi project(':PassThroughAdsComponent').projectDir = new File(rootProject.projectDir, '../PassThroughAdsComponent') project(':PassThroughLoginComponent').projectDir = new File(rootProject.projectDir, '../PassThroughLoginComponent') project(':LoggerAnalyticsComponent').projectDir = new File(rootProject.projectDir, '../LoggerAnalyticsComponent') +// Uncomment when using ComScore Analytics +//project(':comscore').projectDir = new File(rootProject.projectDir, '../ComScoreAnalyticsComponent/libs/comscore') diff --git a/ComScoreAnalyticsComponent/build.gradle b/ComScoreAnalyticsComponent/build.gradle new file mode 100644 index 0000000..6649c8e --- /dev/null +++ b/ComScoreAnalyticsComponent/build.gradle @@ -0,0 +1,54 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 24 + buildToolsVersion "24.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + packagingOptions { + pickFirst 'META-INF/LICENSE' + } +} + + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + compile project(':ModuleInterface') + compile project(':AnalyticsInterface') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:24.2.1' + testCompile 'junit:junit:4.12' + compile project(':comscore') +} + diff --git a/ComScoreAnalyticsComponent/libs/comscore/build.gradle b/ComScoreAnalyticsComponent/libs/comscore/build.gradle new file mode 100644 index 0000000..418f075 --- /dev/null +++ b/ComScoreAnalyticsComponent/libs/comscore/build.gradle @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file('comscore.aar')) \ No newline at end of file diff --git a/ComScoreAnalyticsComponent/libs/comscore/comscore.aar b/ComScoreAnalyticsComponent/libs/comscore/comscore.aar new file mode 100644 index 0000000..d6a7646 Binary files /dev/null and b/ComScoreAnalyticsComponent/libs/comscore/comscore.aar differ diff --git a/ComScoreAnalyticsComponent/proguard-rules.pro b/ComScoreAnalyticsComponent/proguard-rules.pro new file mode 100644 index 0000000..743015c --- /dev/null +++ b/ComScoreAnalyticsComponent/proguard-rules.pro @@ -0,0 +1,19 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/sferris/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} +-keep class com.comscore.** { *; } +-dontwarn com.comscore.** \ No newline at end of file diff --git a/ComScoreAnalyticsComponent/src/main/AndroidManifest.xml b/ComScoreAnalyticsComponent/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b0a36d0 --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreCustomAnalyticsTags.json b/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreCustomAnalyticsTags.json new file mode 100644 index 0000000..9eae737 --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreCustomAnalyticsTags.json @@ -0,0 +1,15 @@ +{ + "ATTRIBUTE_TITLE": "ns_st_pr", + "ATTRIBUTE_PUBLISHER_NAME": "ns_st_pu", + "ATTRIBUTE_SEASON_NUMBER": "ns_st_sn", + "ATTRIBUTE_EPISODE_NUMBER": "ns_st_en", + "ATTRIBUTE_GENRE": "ns_st_ge", + "ATTRIBUTE_AIRDATE": "ns_st_ddt", + "ATTRIBUTE_SEGMENT_NUMBER": "ns_st_pn", + "ATTRIBUTE_NUMBER_OF_SEGMENTS": "ns_st_tp", + "ATTRIBUTE_SUBTITLE": "ns_st_ep", + "ATTRIBUTE_VIDEO_DURATION": "ns_st_cl", + "ATTRIBUTE_VIDEO_ID": "ns_st_ci", + "ATTRIBUTE_AD_ID": "ns_st_ci", + "ATTRIBUTE_ADVERTISEMENT_TYPE": "ns_st_ad" +} \ No newline at end of file diff --git a/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreEventTags.json b/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreEventTags.json new file mode 100644 index 0000000..aee2fd3 --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/assets/configurations/ComScoreEventTags.json @@ -0,0 +1,33 @@ +{ + "hidden": [ + "ACTION_PURCHASE_INITIATED", + "ACTION_PURCHASE_COMPLETE", + "ACTION_AUTHENTICATION_REQUESTED", + "ACTION_AUTHENTICATION_SUCCEEDED", + "ACTION_AUTHENTICATION_FAILED", + "ACTION_AUTHORIZATION_REQUESTED", + "ACTION_AUTHORIZATION_SUCCEEDED", + "ACTION_AUTHORIZATION_FAILED", + "ACTION_LOG_OUT_REQUESTED", + "ACTION_LOG_OUT_SUCCEEDED", + "ACTION_LOG_OUT_FAILED", + "ACTION_APP_AUTHENTICATION_STATUS_BROADCAST", + "ACTION_APP_AUTHENTICATION_STATUS_REQUESTED_BY_LAUNCHER" + ], + "view": [ + "ACTION_PLAYBACK_CONTROL_PRE", + "ACTION_PLAYBACK_CONTROL_NEXT", + "ACTION_PLAYBACK_CONTROL_TOGGLE_CC", + "ACTION_PLAYBACK_CONTROL_REWIND", + "ACTION_PLAYBACK_CONTROL_MULTI_ACTION", + "ACTION_REQUEST_FROM_LAUNCHER_TO_PLAY_CONTENT", + "ACTION_RECOMMENDED_CONTENT_CLICKED", + "ACTION_DELETE_RECOMMENDATION_SERVICE_CALLED", + "ACTION_UPDATE_GLOBAL_RECOMMENDATIONS", + "ACTION_DISMISS_RECOMMENDATION_ON_CONTENT_COMPLETE", + "ACTION_REMOVE_EXPIRED_RECOMMENDATIONS", + "ACTION_UPDATE_RELATED_RECOMMENDATIONS", + "ACTION_SEARCH", + "ACTION_ERROR" + ] +} \ No newline at end of file diff --git a/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalytics.java b/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalytics.java new file mode 100644 index 0000000..a4bc4b5 --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalytics.java @@ -0,0 +1,499 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.analytics.comscore; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.amazon.analytics.AnalyticsTags; +import com.amazon.analytics.CustomAnalyticsTags; +import com.amazon.android.utils.FileHelper; +import com.amazon.android.utils.MapHelper; +import com.amazon.utils.security.ResourceObfuscator; +import com.amazon.analytics.IAnalytics; + +import com.comscore.PublisherConfiguration; +import com.comscore.Analytics; +import com.comscore.UsagePropertiesAutoUpdateMode; +import com.comscore.streaming.StreamingAnalytics; + +/** + * An analytics implementation using the comScore framework. + */ +public class ComScoreAnalytics implements IAnalytics { + + /** + * Debug tag. + */ + private static final String TAG = ComScoreAnalytics.class.getSimpleName(); + + /** + * Name used for implementation creator registration to Module Manager. + */ + static final String IMPL_CREATOR_NAME = ComScoreAnalytics.class.getSimpleName(); + + /** + * String used for adding Action in ComScore hidden and screen view events + */ + private static final String ACTION = "ACTION"; + + /** + * String used for tracking hidden events parsed from json event tags file + */ + private static final String HIDDEN = "hidden"; + + /** + * String used for tracking screen view events parsed from json event tags file + */ + private static final String VIEW = "view"; + + /** + * ComScore analytics object + */ + private StreamingAnalytics mStreamingAnalytics; + + /** + * Metadata for the current content and ads + */ + private HashMap mCurrentContentMetadata; + private HashMap mCurrentAdMetadata; + + /** + * HashSets for custom event tags received from parsing custom events json file + */ + private Set mHiddenEventSet; + private Set mViewEventSet; + + // Is the playback screen the current screen? + private boolean mOnPlaybackScreen = false; + + private CustomAnalyticsTags mCustomTags = new CustomAnalyticsTags(); + + /** + * HashMap containing string as keys and HashSets of custom event tags as values + */ + private HashMap> mCustomEventTags = new HashMap<>(); + + /** + * Initialize the custom event map from the given file. + * + * @param context Context. + * @param fileNameId File name ID of the file that contains the custom tag mapping. + */ + private void initEventTags(Context context, int fileNameId) { + + // Read the file only if it exists. + if (FileHelper.doesFileExist(context, context.getResources().getString(fileNameId))) { + mCustomEventTags = MapHelper.loadArrayMappingFromJsonFile(context, fileNameId); + if (mCustomEventTags != null) { + mHiddenEventSet = mCustomEventTags.get(HIDDEN); + mViewEventSet = mCustomEventTags.get(VIEW); + } + Log.d(TAG, "Custom event tags initialized"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void configure(Context context) { + + try { + // Setup comScore object + String id = getClientId(context); + String secret = getPublisherSecret(context); + PublisherConfiguration myPublisherConfig = new PublisherConfiguration.Builder() + .publisherId(getClientId(context)) + .publisherSecret(getPublisherSecret(context)) + .usagePropertiesAutoUpdateMode(UsagePropertiesAutoUpdateMode.FOREGROUND_ONLY) + .build(); + Analytics.getConfiguration().addClient(myPublisherConfig); + Analytics.start(context); + + } + catch (Exception e) { + Log.e(TAG, "Configuration failed", e); + return; + } + + // Setup StreamingAnalytics object + mStreamingAnalytics = new StreamingAnalytics(); + + mCustomTags.init(context, R.string.comscore_analytics_custom_tags); + initEventTags(context, R.string.comscore_analytics_event_tags); + + Log.d(TAG, "Configuration complete"); + } + + /** + * Unobfuscate the ComScore client id. + * + * @param context Context. + * @return The unobfuscated ComScore client id. + */ + private String getClientId(Context context) throws Exception { + + return ResourceObfuscator.unobfuscate(context.getString(R.string.encrypted_comscore_client_id), + getRandomStringsForKey(context), + getRandomStringsForIv(context)); + } + + /** + * Unobfuscate the ComScore publisher secret. + * + * @param context Context. + * @return The unobfuscated Comscore publisher secret. + */ + private String getPublisherSecret(Context context) throws Exception { + + return ResourceObfuscator.unobfuscate(context.getString(R.string.encrypted_comscore_publisher_secret), + getRandomStringsForKey(context), + getRandomStringsForIv(context)); + } + + /** + * Get a list of "random" strings for key. + * + * @param context Context to access resources. + * @return List of strings. + */ + private static String[] getRandomStringsForKey(Context context) { + + return new String[]{ + context.getString(R.string.comscore_key_1), + context.getString(R.string.comscore_key_2), + context.getString(R.string.comscore_key_3) + }; + } + + /** + * Get random strings for use with initialization vector. + * + * @param context Context to access resources. + * @return List of strings. + */ + private static String[] getRandomStringsForIv(Context context) { + + return new String[]{ + context.getString(R.string.comscore_key_4), + context.getString(R.string.comscore_key_5), + context.getString(R.string.comscore_key_6) + }; + } + + /** + * {@inheritDoc} + */ + @Override + public void collectLifeCycleData(Activity activity, boolean active) { + + String activityName = activity.getClass().getName(); + if (active) { + Log.d(TAG, "Collecting lifecycle data for " + activityName); + Analytics.notifyEnterForeground(); + } + else { + Log.d(TAG, "Ending lifecycle data collection for " + activityName); + Analytics.notifyExitForeground(); + } + } + + /** + * Check for hidden event (Fire app builder track these events internally with name action, + * While ComScore treat them as hidden / screen view events) + * + * @param event current event happened in system which need to be provided to analytics module. + * @return boolean response whether the current event is a hidden event + */ + private boolean isHiddenEvent(String event) { + + return (mHiddenEventSet != null && mHiddenEventSet.contains(event)); + } + + /** + * Check for screen view event (Fire app builder track these events internally with name action, + * While ComScore treat them as hidden / screen view events) + * + * @param event current event happened in system which need to be provided to analytics module. + * @return boolean response whether the current event is a screen view event + */ + private boolean isScreenViewEvent(String event) { + + return (mViewEventSet != null && mViewEventSet.contains(event)); + } + + /** + * {@inheritDoc} + */ + @Override + public void trackAction(HashMap data) { + + String action = (String) data.get(AnalyticsTags.ACTION_NAME); + Log.d(TAG, "Tracking Action: " + action); + + @SuppressWarnings("unchecked") + HashMap attributes = + (HashMap) data.get(AnalyticsTags.ATTRIBUTES); + + if (attributes == null) { + Log.d(TAG, "Action has no attribute/value pairs"); + return; + } + // Log action attributes + Log.d(TAG, "Action has the following attributes: " + attributes.toString()); + + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_STARTED)) { + trackPlaybackStarted(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_AD_START)) { + trackAdStarted(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_AD_COMPLETE)) { + trackAdFinished(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_CONTROL_PAUSE)) { + trackPlaybackPaused(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_CONTROL_PLAY)) { + trackPlaybackResumed(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_BUFFER_START)) { + trackBufferingStarted(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_BUFFER_END)) { + trackBufferingEnded(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_CONTROL_FF) || + action.equals(AnalyticsTags.ACTION_PLAYBACK_CONTROL_REWIND)) { + trackSeekStarted(attributes); + return; + } + if (action.equals(AnalyticsTags.ACTION_PLAYBACK_FINISHED)) { + trackPlaybackFinished(attributes); + return; + } + if (isHiddenEvent(action)) { + attributes.put(ACTION, action); + trackHiddenEvents(attributes); + return; + } + if (isScreenViewEvent(action)) { + attributes.put(ACTION, action); + trackScreenViewEvents(attributes); + return; + } + } + + /** + * Tell ComScore that a hidden event has occurred. + * Any event that is not considered to be a app start event or screen view event + * + * @param attributes Attributes of the hidden event. + */ + private void trackHiddenEvents(HashMap attributes) { + + HashMap hiddenEventData = convertMapToStrings(attributes); + Analytics.notifyHiddenEvent(hiddenEventData); + } + + /** + * Tell ComScore that a view event has occurred. + * A screen view event indicates that application screen has changed substantially (50% or more) + * + * @param attributes Attributes of the screen view event. + */ + private void trackScreenViewEvents(HashMap attributes) { + + HashMap viewEventData = convertMapToStrings(attributes); + Analytics.notifyViewEvent(viewEventData); + } + + /** + * Tell ComScore that new content started playing. + * + * @param attributes Attributes of the new content. + */ + private void trackPlaybackStarted(HashMap attributes) { + + mCurrentContentMetadata = convertMapToStrings(attributes); + mStreamingAnalytics.createPlaybackSession(); + mStreamingAnalytics.getPlaybackSession().setAsset(mCustomTags.getCustomTags + (mCurrentContentMetadata, false)); + mStreamingAnalytics.notifyPlay(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that an ad started playing. + * + * @param attributes Attributes for the ad. + */ + private void trackAdStarted(HashMap attributes) { + + mCurrentAdMetadata = convertMapToStrings(attributes); + mStreamingAnalytics.getPlaybackSession().setAsset(mCustomTags.getCustomTags + (mCurrentAdMetadata, false)); + mStreamingAnalytics.notifyPlay(0); + } + + /** + * Tell ComScore that an ad finished playing. + * + * @param attributes Attributes for the ad. + */ + private void trackAdFinished(HashMap attributes) { + + mCurrentAdMetadata = convertMapToStrings(attributes); + mStreamingAnalytics.getPlaybackSession().setAsset(mCustomTags.getCustomTags + (mCurrentAdMetadata, false)); + mStreamingAnalytics.notifyEnd(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that playback was paused. + * + * @param attributes Attributes for the content that was paused. + */ + private void trackPlaybackPaused(HashMap attributes) { + + mStreamingAnalytics.notifyPause(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that playback was resumed. + * + * @param attributes Attributes for the content that was resumed. + */ + private void trackPlaybackResumed(HashMap attributes) { + + mStreamingAnalytics.notifyPlay(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that a buffer event started during playback. + * + * @param attributes Attributes for the content that was playing when the buffer event started. + */ + private void trackBufferingStarted(HashMap attributes) { + + mStreamingAnalytics.notifyBufferStart(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that a buffer event ended. + * + * @param attributes Attributes for the content that was playing when the buffer event started. + */ + private void trackBufferingEnded(HashMap attributes) { + + mStreamingAnalytics.notifyBufferStop(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that a seek event started during playback. + * + * @param attributes Attributes for the content that was playing when the seek event started. + */ + private void trackSeekStarted(HashMap attributes) { + + mStreamingAnalytics.notifySeekStart(getCurrentPosition(attributes)); + } + + /** + * Tell ComScore that playback finished for the current content. + */ + private void trackPlaybackFinished(HashMap attributes) { + + mCurrentContentMetadata = null; + mCurrentAdMetadata = null; + mStreamingAnalytics.notifyEnd(getCurrentPosition(attributes)); + } + + /** + * Get the current position attribute. + * + * @param attributes Attributes for the current content. + * @return The current position attribute, or 0 if it is not defined. + */ + private long getCurrentPosition(HashMap attributes) { + + if (attributes.containsKey(AnalyticsTags.ATTRIBUTE_VIDEO_CURRENT_POSITION)) { + return Long.parseLong( + String.valueOf(attributes.get(AnalyticsTags.ATTRIBUTE_VIDEO_CURRENT_POSITION))); + } + return 0; + } + + /** + * Convert the given attributes map types from to + * + * @param attributes Original attributes map to convert. + * @return The original attributes map converted to types + */ + private HashMap convertMapToStrings(HashMap attributes) { + + HashMap result = new HashMap<>(); + for (Map.Entry entry : attributes.entrySet()) { + result.put(entry.getKey(), String.valueOf(entry.getValue())); + } + return result; + } + + /** + * {@inheritDoc} + */ + @Override + public void trackState(String screen) { + + Log.d(TAG, "Tracking screen " + screen); + // User experience started + if (screen.equals(AnalyticsTags.SCREEN_PLAYBACK)) { + mOnPlaybackScreen = true; + Analytics.notifyUxActive(); + return; + } + // User experience stopped + if (!screen.equals(AnalyticsTags.SCREEN_PLAYBACK) && mOnPlaybackScreen) { + mOnPlaybackScreen = false; + Analytics.notifyUxInactive(); + return; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void trackCaughtError(String errorMessage, Throwable t) { + + Log.e(TAG, errorMessage, t); + if(mStreamingAnalytics != null) { + mStreamingAnalytics.notifyEnd(0); + } + } +} diff --git a/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalyticsImplCreator.java b/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalyticsImplCreator.java new file mode 100644 index 0000000..57f16e6 --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/java/com/amazon/analytics/comscore/ComScoreAnalyticsImplCreator.java @@ -0,0 +1,36 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.analytics.comscore; + + +import com.amazon.android.module.IImplCreator; + +import com.amazon.analytics.IAnalytics; + +/** + * This lets modules follow the same protocol for creating an instance of the ComScore + * Analytics module. + */ +public class ComScoreAnalyticsImplCreator implements IImplCreator { + + /** + * {@inheritDoc} + */ + @Override + public IAnalytics createImpl() { + + return new ComScoreAnalytics(); + } +} diff --git a/ComScoreAnalyticsComponent/src/main/res/values/custom.xml b/ComScoreAnalyticsComponent/src/main/res/values/custom.xml new file mode 100644 index 0000000..e8319ca --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/res/values/custom.xml @@ -0,0 +1,33 @@ + + + + + + encrypted_comscore_client_id + + + encrypted_comscore_publisher_secret + + + comscore_key_1 + comscore_key_2 + comscore_key_3 + comscore_key_4 + comscore_key_5 + comscore_key_6 + + diff --git a/ComScoreAnalyticsComponent/src/main/res/values/values.xml b/ComScoreAnalyticsComponent/src/main/res/values/values.xml new file mode 100644 index 0000000..a05d09d --- /dev/null +++ b/ComScoreAnalyticsComponent/src/main/res/values/values.xml @@ -0,0 +1,24 @@ + + + + + + configurations/ComScoreCustomAnalyticsTags.json + + + configurations/ComScoreEventTags.json + + diff --git a/ComponentTestFramework/EmptyAndroidApp/settings.gradle b/ComponentTestFramework/EmptyAndroidApp/settings.gradle index 33c2e55..cd84c3a 100644 --- a/ComponentTestFramework/EmptyAndroidApp/settings.gradle +++ b/ComponentTestFramework/EmptyAndroidApp/settings.gradle @@ -34,13 +34,17 @@ include ':app', ':AmazonInAppPurchaseComponent', ':AMZNMediaPlayerComponent', ':UAMP', + ':BrightCoveMediaPlayerComponent', ':ContentBrowser', ':CrashlyticsComponent', ':FlurryAnalyticsComponent', ':FreeWheelAdsComponent', ':GoogleAnalyticsComponent', + ':ImageViewerComponent', ':VastAdsComponent', ':TVUIComponent', + ':comscore', + ':ComScoreAnalyticsComponent', ':Application' project(':Utils').projectDir = new File(rootProject.projectDir, "../../Utils") @@ -63,12 +67,16 @@ project(':AdobepassAuthComponent').projectDir = new File(rootProject.projectDir, project(':AmazonInAppPurchaseComponent').projectDir = new File(rootProject.projectDir, "../../AmazonInAppPurchaseComponent") project(':AMZNMediaPlayerComponent').projectDir = new File(rootProject.projectDir, "../../AMZNMediaPlayerComponent") project(':UAMP').projectDir = new File(rootProject.projectDir, "../../UAMP") +project(':BrightCoveMediaPlayerComponent').projectDir = new File(rootProject.projectDir, "../../BrightCoveMediaPlayerComponent") project(':ContentBrowser').projectDir = new File(rootProject.projectDir, "../../ContentBrowser") project(':CrashlyticsComponent').projectDir = new File(rootProject.projectDir, "../../CrashlyticsComponent") project(':FlurryAnalyticsComponent').projectDir = new File(rootProject.projectDir, "../../FlurryAnalyticsComponent") project(':FreeWheelAdsComponent').projectDir = new File(rootProject.projectDir, "../../FreeWheelAdsComponent") project(':GoogleAnalyticsComponent').projectDir = new File(rootProject.projectDir, "../../GoogleAnalyticsComponent") +project(':ImageViewerComponent').projectDir = new File(rootProject.projectDir, "../../ImageViewerComponent") project(':VastAdsComponent').projectDir = new File(rootProject.projectDir, "../../VastAdsComponent") project(':Application').projectDir = new File(rootProject.projectDir, "../../Application/app") project(':TVUIComponent').projectDir = new File(rootProject.projectDir, "../../TVUIComponent/lib") +project(':ComScoreAnalyticsComponent').projectDir = new File(rootProject.projectDir, "../../ComScoreAnalyticsComponent") +project(':comscore').projectDir = new File(rootProject.projectDir, '../../ComScoreAnalyticsComponent/libs/comscore') diff --git a/ComponentTestFramework/config.json b/ComponentTestFramework/config.json index 59a8f04..bba4847 100644 --- a/ComponentTestFramework/config.json +++ b/ComponentTestFramework/config.json @@ -89,6 +89,10 @@ "componentName": "ContentBrowser", "dir": "../ContentBrowser" }, + { + "componentName": "ImageViewerComponent", + "dir": "../ImageViewerComponent" + }, { "componentName": "AmazonInAppPurchaseComponent", "dir": "../AmazonInAppPurchaseComponent" @@ -96,5 +100,13 @@ { "componentName": "FlurryAnalyticsComponent", "dir": "../FlurryAnalyticsComponent" + }, + { + "componentName": "BrightCoveMediaPlayerComponent", + "dir": "../BrightCoveMediaPlayerComponent" + }, + { + "componentName": "ComScoreAnalyticsComponent", + "dir": "../ComScoreAnalyticsComponent" } ] diff --git a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/ContentBrowser.java b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/ContentBrowser.java index 22dadc7..1638d13 100644 --- a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/ContentBrowser.java +++ b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/ContentBrowser.java @@ -477,6 +477,19 @@ public interface IScreenSwitchListener { void onScreenSwitch(Bundle extra); } + /** + * Screen switch Error listener interface. + */ + public interface IScreenSwitchErrorHandler { + + /** + * Authentication error callback. + * + * @param iScreenSwitchListener Screen switch listener interface implementation. + */ + void onErrorHandler(IScreenSwitchListener iScreenSwitchListener); + } + /** * Constructor. * @@ -833,6 +846,17 @@ public boolean isUseCategoryAsDefaultRelatedContent() { return mNavigator.getNavigatorModel().getConfig().useCategoryAsDefaultRelatedContent; } + /** + * Get the flag for enabling CEA-608 closed captions + * + * @return True if CEA-608 closed captions should be enabled and set as priority; + * false otherwise + */ + public boolean isEnableCEA608() { + + return mNavigator.getNavigatorModel().getConfig().enableCEA608; + } + /** * Get powered by logo url by name. * @@ -1256,14 +1280,50 @@ public void handleOnActivityResult(Activity activity, int requestCode, int resul } } + /** + * show authentication Error Dialog + * + * @param iScreenSwitchListener Screen switch listener. + */ + public void showAuthenticationErrorDialog(IScreenSwitchListener iScreenSwitchListener) { + + AlertDialogFragment.createAndShowAlertDialogFragment( + mNavigator.getActiveActivity(), + mAppContext.getString(R.string.optional_login_dialog_title), + mAppContext.getString(R.string.optional_login_dialog_message), + mAppContext.getString(R.string.now), + mAppContext.getString(R.string.later), + new AlertDialogFragment.IAlertDialogListener() { + + @Override + public void onDialogPositiveButton( + AlertDialogFragment alertDialogFragment) { + + mAuthHelper.handleAuthChain( + iScreenSwitchListener::onScreenSwitch); + } + + @Override + public void onDialogNegativeButton + (AlertDialogFragment alertDialogFragment) { + + Preferences.setBoolean( + AuthHelper.LOGIN_LATER_PREFERENCES_KEY, true); + iScreenSwitchListener.onScreenSwitch(null); + } + }); + } + /** * Verify screen switch. * * @param screenName Screen name * @param iScreenSwitchListener Screen switch listener. + * @param iScreenSwitchErrorHandler Screen switch error handler */ public void verifyScreenSwitch(String screenName, - IScreenSwitchListener iScreenSwitchListener) { + IScreenSwitchListener iScreenSwitchListener, + IScreenSwitchErrorHandler iScreenSwitchErrorHandler) { UINode uiNode = (UINode) mNavigator.getNodeObjectByScreenName(screenName); Log.d(TAG, "VerifyScreenSwitch called in:" + screenName); @@ -1281,34 +1341,10 @@ public void verifyScreenSwitch(String screenName, mAuthHelper.isAuthenticated().subscribe(extras -> { if (extras.getBoolean(AuthHelper.RESULT)) { mAuthHelper.handleAuthChain( - iScreenSwitchListener::onScreenSwitch); + iScreenSwitchListener::onScreenSwitch, extras); } else { - AlertDialogFragment.createAndShowAlertDialogFragment( - mNavigator.getActiveActivity(), - mAppContext.getString(R.string.optional_login_dialog_title), - mAppContext.getString(R.string.optional_login_dialog_message), - mAppContext.getString(R.string.now), - mAppContext.getString(R.string.later), - new AlertDialogFragment.IAlertDialogListener() { - - @Override - public void onDialogPositiveButton( - AlertDialogFragment alertDialogFragment) { - - mAuthHelper.handleAuthChain( - iScreenSwitchListener::onScreenSwitch); - } - - @Override - public void onDialogNegativeButton - (AlertDialogFragment alertDialogFragment) { - - Preferences.setBoolean( - AuthHelper.LOGIN_LATER_PREFERENCES_KEY, true); - iScreenSwitchListener.onScreenSwitch(null); - } - }); + iScreenSwitchErrorHandler.onErrorHandler(iScreenSwitchListener); } }); } @@ -1342,7 +1378,8 @@ public void switchToScreen(String screenName, Navigator.ActivitySwitchListener activitySwitchListener) { verifyScreenSwitch(screenName, extra -> - mNavigator.startActivity(screenName, activitySwitchListener) + mNavigator.startActivity(screenName, activitySwitchListener), + errorExtra -> showAuthenticationErrorDialog(errorExtra) ); } @@ -1355,7 +1392,8 @@ public void switchToScreen(String screenName, Navigator.ActivitySwitchListener public void switchToScreen(String screenName, Bundle bundle) { verifyScreenSwitch(screenName, extra -> - mNavigator.startActivity(screenName, bundle) + mNavigator.startActivity(screenName, bundle), + errorExtra -> showAuthenticationErrorDialog(errorExtra) ); } diff --git a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AnalyticsHelper.java b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AnalyticsHelper.java index ee4072d..3c6b324 100644 --- a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AnalyticsHelper.java +++ b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AnalyticsHelper.java @@ -15,6 +15,7 @@ package com.amazon.android.contentbrowser.helper; +import com.amazon.ads.AdMetaData; import com.amazon.analytics.ExtraContentAttributes; import com.amazon.analytics.IAnalytics; import com.amazon.android.contentbrowser.ContentBrowser; @@ -63,6 +64,13 @@ private static Map getBasicAnalyticsAttributesForContent(Content HashMap analyticsAttributes = new HashMap<>(); + // If the app is launched from a playback request from launcher the content here may be null + // because content browser has no selected content yet. + if (content == null) { + Log.e(TAG, "Content is null when trying to get basic analytics attributes."); + return analyticsAttributes; + } + // Set up the movie attributes. analyticsAttributes.put(AnalyticsTags.ATTRIBUTE_TITLE, content.getTitle()); @@ -248,6 +256,123 @@ public static void trackPurchaseResult(String sku, boolean purchaseResult) { sendAnalytics(AnalyticsTags.ACTION_PURCHASE_COMPLETE, attributes); } + /** + * Tracks Authentication Request. + * + */ + public static void trackAuthenticationRequest() { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHENTICATION_SUBMITTED, 1); + + sendAnalytics(AnalyticsTags.ACTION_AUTHENTICATION_REQUESTED, attributes); + } + + /** + * Tracks Authentication Successes. + * + */ + public static void trackAuthenticationResultSuccess() { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHENTICATION_SUCCESS, 1); + + sendAnalytics(AnalyticsTags.ACTION_AUTHENTICATION_SUCCEEDED, attributes); + } + + /** + * Tracks Authentication Failures. + * + * @param failureReason Failure Reason Registration/Network/Authentication/Authorization + */ + public static void trackAuthenticationResultFailure(String failureReason) { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHENTICATION_FAILURE, 1); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHENTICATION_FAILURE_REASON, failureReason); + + sendAnalytics(AnalyticsTags.ACTION_AUTHENTICATION_FAILED, attributes); + } + + /** + * Tracks Authorization Requests. + * + * @param content selected content + */ + public static void trackAuthorizationRequest(Content content) { + + Map attributes = getBasicAnalyticsAttributesForContent(content); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHORIZATION_SUBMITTED, 1); + + sendAnalytics(AnalyticsTags.ACTION_AUTHORIZATION_REQUESTED, attributes); + } + + /** + * Tracks Authorization Successes. + * + * @param content selected content + */ + public static void trackAuthorizationResultSuccess(Content content) { + + Map attributes = getBasicAnalyticsAttributesForContent(content); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHORIZATION_SUCCESS, 1); + + sendAnalytics(AnalyticsTags.ACTION_AUTHORIZATION_SUCCEEDED, attributes); + } + + /** + * Tracks Authorization Failures. + * + * @param content selected content + * @param failureReason Failure Reason Action/Bad Request/Internal/Network/Unknown + */ + public static void trackAuthorizationResultFailure(Content content, String failureReason) { + + Map attributes = getBasicAnalyticsAttributesForContent(content); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHORIZATION_FAILURE, 1); + attributes.put(AnalyticsTags.ATTRIBUTE_AUTHORIZATION_FAILURE_REASON, failureReason); + + sendAnalytics(AnalyticsTags.ACTION_AUTHORIZATION_FAILED, attributes); + } + + /** + * Tracks Log Out Request. + * + */ + public static void trackLogOutRequest() { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_LOGOUT_SUBMITTED, 1); + + sendAnalytics(AnalyticsTags.ACTION_LOG_OUT_REQUESTED, attributes); + } + + /** + * Tracks Log Out Successes. + * + */ + public static void trackLogOutResultSuccess() { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_LOGOUT_SUCCESS, 1); + + sendAnalytics(AnalyticsTags.ACTION_LOG_OUT_SUCCEEDED, attributes); + } + + /** + * Tracks Log Out Failures. + * + * @param failureReason Failure Reason Action/Bad Request/Internal/Network/Unknown + */ + public static void trackLogOutResultFailure(String failureReason) { + + Map attributes = new HashMap<>(); + attributes.put(AnalyticsTags.ATTRIBUTE_LOGOUT_FAILURE, 1); + attributes.put(AnalyticsTags.ATTRIBUTE_LOGOUT_FAILURE_REASON, failureReason); + + sendAnalytics(AnalyticsTags.ACTION_LOG_OUT_FAILED, attributes); + } + /** * Tracks when users interact with the playback controls of the player. * @@ -299,11 +424,13 @@ public static void trackError(String tag, String errorMessage, Exception e) { * * @param content Content during which the ad was started. * @param currentPosition Current position in the content playback that the ad started at. + * @param adMetaData metaData containing ad details. */ - public static void trackAdStarted(Content content, long currentPosition) { + public static void trackAdStarted(Content content, long currentPosition, AdMetaData + adMetaData) { commonAdTrackingSteps(content, AnalyticsTags.ACTION_AD_START, new HashMap<>(), - currentPosition); + currentPosition, adMetaData); } /** @@ -312,16 +439,16 @@ public static void trackAdStarted(Content content, long currentPosition) { * SDK 4326 * * @param content Content during which the ad was played. - * @param duration Duration of ad run. * @param currentPosition Current position in the content playback that the ad finished at. + * @param adMetaData metaData containing ad details. */ - public static void trackAdEnded(Content content, long duration, long currentPosition) { + public static void trackAdEnded(Content content, long currentPosition, AdMetaData adMetaData) { HashMap attributes = new HashMap<>(); - attributes.put(AnalyticsTags.ATTRIBUTE_AD_SECONDS_WATCHED, duration); + attributes.put(AnalyticsTags.ATTRIBUTE_AD_SECONDS_WATCHED, adMetaData.getDurationPlayed()); commonAdTrackingSteps(content, AnalyticsTags.ACTION_AD_COMPLETE, attributes, - currentPosition); + currentPosition, adMetaData); } /** @@ -331,12 +458,15 @@ public static void trackAdEnded(Content content, long duration, long currentPosi * @param action Action to track. * @param attributes Attributes map to be added to action map. * @param currentPosition Current position in the content playback that the ad played. + * @param adMetaData metaData containing ad details. */ private static void commonAdTrackingSteps(Content content, String action, HashMap attributes, - long currentPosition) { - //TODO: add ad id SDK 4326 - attributes.put(AnalyticsTags.ATTRIBUTE_AD_ID, ""); + long currentPosition, AdMetaData adMetaData) { + + attributes.put(AnalyticsTags.ATTRIBUTE_AD_ID, adMetaData.getAdId()); + attributes.put(AnalyticsTags.ATTRIBUTE_AD_DURATION, adMetaData.getDurationReceived()); + attributes.put(AnalyticsTags.ATTRIBUTE_ADVERTISEMENT_TYPE, adMetaData.getAdType()); attributes.putAll(getBasicAnalyticsAttributesForContent(content)); attributes.put(AnalyticsTags.ATTRIBUTE_VIDEO_CURRENT_POSITION, currentPosition); diff --git a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AuthHelper.java b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AuthHelper.java index e05c4b0..17e1508 100644 --- a/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AuthHelper.java +++ b/ContentBrowser/src/main/java/com/amazon/android/contentbrowser/helper/AuthHelper.java @@ -17,6 +17,7 @@ import com.amazon.android.contentbrowser.ContentBrowser; import com.amazon.android.contentbrowser.R; +import com.amazon.android.model.content.Content; import com.amazon.android.module.ModuleManager; import com.amazon.android.ui.constants.PreferencesConstants; import com.amazon.android.ui.fragments.ErrorDialogFragment; @@ -131,6 +132,11 @@ public boolean isUserAuthenticated() { */ private static final String DEFAULT_MVPD_URL = "DEFAULT_MVPD_URL"; + /** + * String constant to be used as default error in authentication events. + */ + private static final String DEFAULT_AUTH_ERROR = "Unknown"; + /** * Authentication implementation reference. */ @@ -266,6 +272,22 @@ private void handleFailureCase(Subscriber subscriber, Bundle extras) { subscriber.onCompleted(); } + /** + * retrieve Error Category. + * + * @param extras Bundle. + * @return Error category. + */ + String retrieveErrorCategory(Bundle extras){ + Bundle bundle = extras.getBundle(AuthenticationConstants.ERROR_BUNDLE); + String authErrorCategory = DEFAULT_AUTH_ERROR; + if (bundle != null) { + authErrorCategory = bundle.getString(AuthenticationConstants.ERROR_CATEGORY, + DEFAULT_AUTH_ERROR); + } + return authErrorCategory; + } + /** * Logout observable. * @@ -274,12 +296,13 @@ private void handleFailureCase(Subscriber subscriber, Bundle extras) { public Observable logout() { Log.v(TAG, "logout called."); - + AnalyticsHelper.trackLogOutRequest(); return Observable.create(subscriber -> { mIAuthentication.logout(mAppContext, new IAuthentication.ResponseHandler() { @Override public void onSuccess(Bundle extras) { + AnalyticsHelper.trackLogOutResultSuccess(); broadcastAuthenticationStatus(false); Log.d(TAG, "Account logout success"); handleSuccessCase(subscriber, extras); @@ -288,6 +311,7 @@ public void onSuccess(Bundle extras) { @Override public void onFailure(Bundle extras) { + AnalyticsHelper.trackLogOutResultFailure(retrieveErrorCategory(extras)); Log.e(TAG, "Account logout failure"); handleFailureCase(subscriber, extras); } @@ -331,6 +355,9 @@ public void onFailure(Bundle extras) { */ public Observable isAuthorized() { + //get the requested content + Content content = mContentBrowser.getLastSelectedContent(); + AnalyticsHelper.trackAuthorizationRequest(content); return Observable.create(subscriber -> { // Check if user is logged in. If not, show authentication activity. mIAuthentication.isResourceAuthorized(mAppContext, "", @@ -340,6 +367,8 @@ public void onSuccess(Bundle extras) { Log.d(TAG, "Resource Authorization " + "success"); + AnalyticsHelper + .trackAuthorizationResultSuccess(content); handleSuccessCase(subscriber, extras); } @@ -348,6 +377,8 @@ public void onFailure(Bundle extras) { Log.e(TAG, "Resource Authorization " + "failed"); + AnalyticsHelper + .trackAuthorizationResultFailure(content, retrieveErrorCategory(extras)); handleFailureCase(subscriber, extras); } }); @@ -388,6 +419,7 @@ private void handleAuthenticationActivityResultBundle(Bundle bundle) { */ public Observable authenticateWithActivity() { + AnalyticsHelper.trackAuthenticationRequest(); return mRxLauncher.from(mContentBrowser.getNavigator() .getActiveActivity()) .startActivityForResult(getIAuthentication() @@ -405,6 +437,21 @@ public Observable authenticateWithActivity() { else if (activityResult.data != null) { resultBundle = activityResult.data.getExtras(); } + else { + // Cancel auth request. + cancelAllRequests(); + return resultBundle; + } + + //Check if authentication succeeded. + if (activityResult.isOk()) { + AnalyticsHelper.trackAuthenticationResultSuccess(); + } + else { + AnalyticsHelper.trackAuthenticationResultFailure + (retrieveErrorCategory(resultBundle)); + } + handleAuthenticationActivityResultBundle(resultBundle); if (resultBundle != null) { @@ -425,24 +472,52 @@ private Observable authenticate() { return isAuthenticated().flatMap( // With isAuthenticated result bundle do - isAuthenticatedResultBundle -> - Observable.create(new OperatorIfThen<>( - // If isAuthenticated success then do - // isAuthorized. - () -> isAuthenticatedResultBundle.getBoolean - (RESULT), - isAuthorized(), - // If isAuthenticated failed then do - // authenticateWithActivity. - // Warning!!! After this point all the - // upcoming tasks needs to be handled - // in upcoming activity!!! - authenticateWithActivity() - ) - ) + isAuthenticatedResultBundle -> { + if (isAuthenticatedResultBundle.getBoolean(RESULT)) { + // If isAuthenticated success then do isAuthorized. + return isAuthorized(); + } + else { + // If isAuthenticated failed then do + // authenticateWithActivity. + // Warning!!! After this point all the + // upcoming tasks needs to be handled + // in upcoming activity!!! + return authenticateWithActivity(); + } + } ); } + /** + * Handle Authentication Chain. + * + * @param iAuthorizedHandler Authorized handler. + * @param authenticationResult Bundle + */ + public void handleAuthChain(IAuthorizedHandler iAuthorizedHandler, Bundle authenticationResult) { + + if (authenticationResult == null) { + Log.w(TAG, "resultBundle is null, user probably pressed back on login screen"); + } + // If we got Authentication Result success + else if (authenticationResult.getBoolean(RESULT)) { + // Check if we are authorized in current activity context. + isAuthorized().subscribe(bundle -> { + // If we were authorized return success. + if (bundle.getBoolean(RESULT)) { + iAuthorizedHandler.onAuthorized(bundle); + } else { + // If we were not authorized return show error. + handleErrorBundle(bundle); + } + }); + } else { + // If everything failed then show error. + handleErrorBundle(authenticationResult); + } + } + /** * Handle Authentication Chain. * @@ -454,24 +529,34 @@ public void handleAuthChain(IAuthorizedHandler iAuthorizedHandler) { // Check authentication first. authenticate() .subscribe(resultBundle -> { - if(resultBundle == null) { - Log.w(TAG, "resultBundle is null, user probably pressed back on login screen"); + if (resultBundle == null) { + Log.w(TAG, "resultBundle is null, user probably pressed back on login " + + "screen"); } // If we got a login screen and login was success else if (resultBundle.getBoolean(RESULT_FROM_ACTIVITY)) { - // Check if we are authorized in upcoming activity context. - mContentBrowser - .getNavigator() - .runOnUpcomingActivity(() -> isAuthorized().subscribe(bundle -> { - // If we were authorized return success. - if (resultBundle.getBoolean(RESULT)) { - iAuthorizedHandler.onAuthorized(resultBundle); - } - else { - // If we were not authorized return show error. - handleErrorBundle(resultBundle); - } - })); + //If authentication succeeded + if (resultBundle.getBoolean(RESULT)) { + // Check if we are authorized in upcoming activity context. + mContentBrowser + .getNavigator() + .runOnUpcomingActivity(() -> isAuthorized().subscribe(bundle -> { + // If we were authorized return success. + if (bundle.getBoolean(RESULT)) { + iAuthorizedHandler.onAuthorized(bundle); + } + else { + // If we were not authorized return show error. + handleErrorBundle(bundle); + } + })); + } + else { + // If we were not authenticated return show error. + mContentBrowser.getNavigator() + .runOnUpcomingActivity(() -> handleErrorBundle + (resultBundle)); + } } else if (resultBundle.getBoolean(RESULT)) { // If we were logged in and authorized return success. @@ -479,9 +564,7 @@ else if (resultBundle.getBoolean(RESULT)) { } else { // If everything failed then show error. - mContentBrowser.getNavigator() - .runOnUpcomingActivity(() -> handleErrorBundle - (resultBundle)); + handleErrorBundle(resultBundle); } }, throwable -> Log.e(TAG, "handleAuthChain failed:", throwable)); } @@ -522,10 +605,12 @@ public void cancelAllRequests() { private void logoutFromAccount(Context context) { Log.v(TAG, "logoutFromAccount called."); + AnalyticsHelper.trackLogOutRequest(); mIAuthentication.logout(context, new IAuthentication.ResponseHandler() { @Override public void onSuccess(Bundle extras) { + AnalyticsHelper.trackLogOutResultSuccess(); broadcastAuthenticationStatus(false); Log.d(TAG, "Account logout success"); } @@ -533,6 +618,7 @@ public void onSuccess(Bundle extras) { @Override public void onFailure(Bundle extras) { + AnalyticsHelper.trackLogOutResultFailure(retrieveErrorCategory(extras)); Log.e(TAG, "Account logout failure"); } }); @@ -544,7 +630,7 @@ public void onFailure(Bundle extras) { * @param bundle Auth error bundle. * @return Error category. */ - public static ErrorUtils.ERROR_CATEGORY convertAuthErrorToErrorUtils(Bundle bundle) { + private static ErrorUtils.ERROR_CATEGORY convertAuthErrorToErrorUtils(Bundle bundle) { switch (bundle.getString(AuthenticationConstants.ERROR_CATEGORY)) { case AuthenticationConstants.REGISTRATION_ERROR_CATEGORY: @@ -577,19 +663,24 @@ public void handleErrorBundle(Bundle extras) { (fragment, errorButtonType, errorCategory) -> { if (ErrorUtils.ERROR_BUTTON_TYPE.DISMISS == errorButtonType) { fragment.dismiss(); + mContentBrowser.updateContentActions(); } else if (ErrorUtils.ERROR_BUTTON_TYPE.LOGOUT == errorButtonType) { + AnalyticsHelper.trackLogOutRequest(); mIAuthentication.logout(activity, new IAuthentication.ResponseHandler() { @Override public void onSuccess(Bundle extras) { + AnalyticsHelper.trackLogOutResultSuccess(); broadcastAuthenticationStatus(false); fragment.dismiss(); + mContentBrowser.updateContentActions(); } @Override public void onFailure(Bundle extras) { + AnalyticsHelper.trackLogOutResultFailure(retrieveErrorCategory(extras)); fragment.getArguments() .putString(ErrorDialogFragment.ARG_ERROR_MESSAGE, activity.getResources().getString( diff --git a/DynamicParser/src/main/java/com/amazon/dynamicparser/impl/XmlParser.java b/DynamicParser/src/main/java/com/amazon/dynamicparser/impl/XmlParser.java index 8635047..a7d79d8 100644 --- a/DynamicParser/src/main/java/com/amazon/dynamicparser/impl/XmlParser.java +++ b/DynamicParser/src/main/java/com/amazon/dynamicparser/impl/XmlParser.java @@ -51,12 +51,17 @@ public class XmlParser implements IParser { /** * Constant name tag for node text field. */ - public final String TEXT_TAG = "#text"; + public static final String TEXT_TAG = "#text"; + + /** + * Constant name tag for node cdata field. + */ + public static final String CDATA_TAG = "#cdata-section"; /** * Constant name tag for node attributes. */ - public final String ATTRIBUTES_TAG = "#attributes"; + public static final String ATTRIBUTES_TAG = "#attributes"; /** * Debug tag. @@ -228,7 +233,7 @@ private Map translateNodeListToMap(NodeList root) { Object value; // If the node is a plain text node, assign its text content to 'value'. - if (node.getNodeName().equals(TEXT_TAG)) { + if (node.getNodeName().equals(TEXT_TAG) || node.getNodeName().equals(CDATA_TAG) ) { value = node.getTextContent(); } // Otherwise, recursively add its child nodes to the result. diff --git a/FacebookAuthComponent/src/main/java/com/amazon/android/auth/facebook/FacebookAuthentication.java b/FacebookAuthComponent/src/main/java/com/amazon/android/auth/facebook/FacebookAuthentication.java index 81dc842..02d48b8 100644 --- a/FacebookAuthComponent/src/main/java/com/amazon/android/auth/facebook/FacebookAuthentication.java +++ b/FacebookAuthComponent/src/main/java/com/amazon/android/auth/facebook/FacebookAuthentication.java @@ -17,6 +17,7 @@ import com.amazon.android.utils.Preferences; import com.amazon.auth.IAuthentication; +import com.amazon.auth.AuthenticationConstants; import com.facebook.FacebookSdk; import android.content.Context; @@ -124,8 +125,7 @@ protected void onPostExecute(Map map) { // Encountered an unknown error while checking access token. if (map == null) { - bundle.putString(ResponseHandler.MESSAGE, - context.getString(R.string.error_checking_token)); + populateErrorBundle(bundle, context.getString(R.string.error_checking_token)); responseHandler.onFailure(bundle); return; } @@ -141,10 +141,9 @@ protected void onPostExecute(Map map) { && map.containsKey(IAuthentication.ERROR)) { // Add error message and status code. - bundle.putInt(ResponseHandler.STATUS_CODE, Integer.parseInt(map.get - (ResponseHandler.STATUS_CODE).toString())); - bundle.putString(ResponseHandler.MESSAGE, - map.get(IAuthentication.ERROR).toString()); + populateAuthenticationFailureBundle(Integer.parseInt(map.get + (ResponseHandler.STATUS_CODE).toString()), bundle, map.get + (IAuthentication.ERROR).toString()); } responseHandler.onFailure(bundle); } @@ -161,8 +160,7 @@ protected Map doInBackground(Void... params) { // Access token is null or empty so we know its not valid. else { Log.d(TAG, context.getString(R.string.access_token_null)); - bundle.putString(ResponseHandler.MESSAGE, - context.getString(R.string.access_token_null)); + populateErrorBundle(bundle, context.getString(R.string.access_token_null)); responseHandler.onFailure(bundle); } } @@ -225,4 +223,38 @@ void setAccessToken(String accessToken) { mAccessToken = accessToken; } + + /** + * Bundle to be sent on failures other than Authentication and Authorization + * + * @param bundle Bundle to populate + * @param errorMessage error message received + */ + private void populateErrorBundle(Bundle bundle, String errorMessage) { + + Bundle errorBundle = new Bundle(); + errorBundle.putString(ResponseHandler.MESSAGE, + errorMessage); + errorBundle.putString( + AuthenticationConstants.ERROR_CATEGORY, + AuthenticationConstants.AUTHENTICATION_ERROR_CATEGORY); + bundle.putBundle( + AuthenticationConstants.ERROR_BUNDLE, errorBundle); + } + + /** + * Bundle to be sent on failures other than Authentication and Authorization + * + * @param statusCode status code received because of failure + * @param bundle Bundle to populate + * @param errorMessage error message received + */ + private void populateAuthenticationFailureBundle(int statusCode, Bundle bundle, String + errorMessage) { + + populateErrorBundle(bundle, errorMessage); + bundle.getBundle( + AuthenticationConstants.ERROR_BUNDLE).putInt(ResponseHandler.STATUS_CODE, + statusCode); + } } diff --git a/FreeWheelAdsComponent/src/main/java/com/amazon/ads/android/freewheel/FreeWheelAds.java b/FreeWheelAdsComponent/src/main/java/com/amazon/ads/android/freewheel/FreeWheelAds.java index 3e35f29..31db11f 100644 --- a/FreeWheelAdsComponent/src/main/java/com/amazon/ads/android/freewheel/FreeWheelAds.java +++ b/FreeWheelAdsComponent/src/main/java/com/amazon/ads/android/freewheel/FreeWheelAds.java @@ -210,7 +210,7 @@ public void setCurrentVideoPosition(double position) { (position / 1000)) { Log.d(TAG, "Mid roll matches at:" + position); mMidrollSlotsIsPlayedList.set(i, true); - startSlot(); + startSlot(IAds.MID_ROLL_AD); slot.play(); break; } @@ -219,6 +219,18 @@ public void setCurrentVideoPosition(double position) { } } + /** + * Return true if post roll ads are available; false otherwise. Currently this component does + * not support post roll ads so this method always returns false. + * + * @return False because post roll ads are not supported. + */ + @Override + public boolean isPostRollAvailable() { + + return false; + } + /** * Set activity state. * @@ -359,8 +371,8 @@ public void run(IEvent e) { else { Log.e(TAG, "Request failed. Playing main content."); // Ad request failed, continue with the content. - startSlot(); - endSlot(false); + startSlot(null); + endSlot(null); } } } @@ -420,7 +432,7 @@ public void run(IEvent e) { if (completedSlot.getTimePositionClass() == mAdConstants.TIME_POSITION_CLASS_MIDROLL()) { // Set mid roll flag and end the ad slot. - endSlot(true); + endSlot(IAds.MID_ROLL_AD); } } }); @@ -439,30 +451,37 @@ private void playNextPreroll() { ISlot nextSlot = mPreRollSlots.remove(0); Log.d(TAG, "Playing preroll slot: " + nextSlot.getCustomId()); // Play next pre roll slot. - startSlot(); + startSlot(IAds.PRE_ROLL_AD); nextSlot.play(); } else { Log.d(TAG, "Finished all prerolls. Starting main content."); - endSlot(false); + endSlot(IAds.PRE_ROLL_AD); } } else { - endSlot(false); + endSlot(null); } } /** * Start Ad slot. * Notifies listener and captures ad slot start time. + * + * @param adType type of ad currently being played */ - private void startSlot() { + private void startSlot(final String adType) { new Handler(mActivity.getMainLooper()).post(new Runnable() { public void run() { // Let listener know about Ad slot start event. if (mIAdsEvents != null) { - mIAdsEvents.onAdSlotStarted(null); + // Put ad metadata in a Bundle. + Bundle extras = getBasicAdDetailBundle(); + if(adType != null) { + extras.putString(AD_TYPE, adType); + } + mIAdsEvents.onAdSlotStarted(extras); } } }); @@ -475,9 +494,9 @@ public void run() { * End Ad slot. * Notifies listener, sends duration and disposes AdContext. * - * @param wasAMidRoll True if the ad is a mid roll ad, else false. + * @param adType type of ad currently being played */ - private void endSlot(final boolean wasAMidRoll) { + private void endSlot(final String adType) { new Handler(mActivity.getMainLooper()).post(new Runnable() { public void run() { @@ -485,10 +504,12 @@ public void run() { if (mIAdsEvents != null) { // Calculate how long Ads played. long adSlotTime = SystemClock.elapsedRealtime() - mAdSlotStartTime; - // Put calculated time in an Bundle. - Bundle extras = new Bundle(); - extras.putLong(DURATION, adSlotTime); - extras.putBoolean(WAS_A_MID_ROLL, wasAMidRoll); + // Put calculated time and ad metadata in a Bundle. + Bundle extras = getBasicAdDetailBundle(); + extras.putLong(DURATION_PLAYED, adSlotTime); + if(adType != null) { + extras.putString(AD_TYPE, adType); + } // Let listener know about Ad slot stop event. mIAdsEvents.onAdSlotEnded(extras); } @@ -521,4 +542,16 @@ public Bundle getExtra() { return mExtras; } + + /** + * provide basic ad details + * + * @return bundle containing ad details. + */ + private Bundle getBasicAdDetailBundle() { + + Bundle extras = new Bundle(); + extras.putString(ID, mCurrentVideoAdId); + return extras; + } } diff --git a/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomDimensionTags.json b/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomDimensionTags.json index 7bda024..2b39aec 100644 --- a/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomDimensionTags.json +++ b/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomDimensionTags.json @@ -11,8 +11,8 @@ "ATTRIBUTE_PURCHASE_TYPE": "10", "ATTRIBUTE_PURCHASE_SOURCE": "11", "ATTRIBUTE_APP_NAME": "12", - "ATTRIBUTE_APP_AUTHENTICATION_SOURCE": "13", - "ATTRIBUTE_LOGIN_FAILURE_REASON": "14", + "ATTRIBUTE_AUTHENTICATION_FAILURE_REASON": "13", + "ATTRIBUTE_AUTHORIZATION_FAILURE_REASON": "14", "ATTRIBUTE_LOGOUT_FAILURE_REASON": "15", "ATTRIBUTE_AIRDATE": "16", "ATTRIBUTE_PUBLISHER_NAME": "17", @@ -20,5 +20,4 @@ "ATTRIBUTE_VIDEO_ID": "19", "ATTRIBUTE_CONTENT_AVAILABLE": "20", "ATTRIBUTE_REQUEST_SOURCE": "21" - -} \ No newline at end of file +} diff --git a/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomMetricTags.json b/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomMetricTags.json index d836734..929a258 100644 --- a/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomMetricTags.json +++ b/GoogleAnalyticsComponent/src/main/assets/configurations/GoogleCustomMetricTags.json @@ -3,7 +3,16 @@ "ATTRIBUTE_VIDEO_SECONDS_WATCHED": "2", "ATTRIBUTE_VIDEO_CURRENT_POSITION": "3", "ATTRIBUTE_AD_ID": "4", + "ATTRIBUTE_AUTHENTICATION_SUBMITTED": "5", + "ATTRIBUTE_AUTHENTICATION_SUCCESS": "6", + "ATTRIBUTE_AUTHENTICATION_FAILURE": "7", + "ATTRIBUTE_AUTHORIZATION_SUBMITTED": "8", + "ATTRIBUTE_AUTHORIZATION_SUCCESS": "9", + "ATTRIBUTE_AUTHORIZATION_FAILURE": "10", "ATTRIBUTE_VIDEO_DURATION": "11", "ATTRIBUTE_EXPIRED_RECOMMENDATIONS_COUNT": "12", - "ATTRIBUTE_RECOMMENDATION_ID": "13" -} \ No newline at end of file + "ATTRIBUTE_RECOMMENDATION_ID": "13", + "ATTRIBUTE_LOGOUT_SUBMITTED": "14", + "ATTRIBUTE_LOGOUT_SUCCESS": "15", + "ATTRIBUTE_LOGOUT_FAILURE": "16" +} diff --git a/LoginWithAmazonComponent/src/main/java/com/amazon/loginwithamazon/LoginWithAmazonAuthentication.java b/LoginWithAmazonComponent/src/main/java/com/amazon/loginwithamazon/LoginWithAmazonAuthentication.java index e96924b..efe04a9 100644 --- a/LoginWithAmazonComponent/src/main/java/com/amazon/loginwithamazon/LoginWithAmazonAuthentication.java +++ b/LoginWithAmazonComponent/src/main/java/com/amazon/loginwithamazon/LoginWithAmazonAuthentication.java @@ -24,6 +24,7 @@ import com.amazon.auth.IAuthentication; +import com.amazon.auth.AuthenticationConstants; import com.amazon.identity.auth.device.shared.APIListener; import com.amazon.android.utils.Preferences; @@ -95,6 +96,8 @@ public void isUserLoggedIn(Context context, final ResponseHandler responseHandle public void onSuccess(Bundle bundle) { // If the token is null then return false. if (bundle.get(mContext.getString(R.string.COM_AMAZON_IDENTITY_AUTH_DEVICE_AUTHORIZATION_TOKEN)) == null) { + populateErrorBundle(bundle, AuthenticationConstants + .AUTHENTICATION_ERROR_CATEGORY); responseHandler.onFailure(bundle); } else { @@ -106,6 +109,7 @@ public void onSuccess(Bundle bundle) { @Override public void onError(AuthError authError) { // There is some other auth issue. + populateErrorBundle(bundle, String.valueOf(authError.getCategory())); responseHandler.onFailure(bundle); } }); @@ -136,19 +140,22 @@ public void isResourceAuthorized(Context context, String resourceId, ResponseHan @Override public void logout(Context context, final ResponseHandler responseHandler) { + final Bundle bundle = new Bundle(); + mAuthManager.clearAuthorizationState(new APIListener() { @Override public void onSuccess(Bundle results) { Preferences.setBoolean(IS_LOGGED_IN, false); - responseHandler.onSuccess(new Bundle()); + responseHandler.onSuccess(bundle); } @Override public void onError(AuthError authError) { Log.e(TAG, "Error clearing authorization state.", authError); - responseHandler.onFailure(new Bundle()); + populateErrorBundle(bundle, String.valueOf(authError.getCategory())); + responseHandler.onFailure(bundle); } }); } @@ -161,4 +168,19 @@ public void cancelAllRequests() { } + /** + * Bundle to be sent on failures other than Authentication and Authorization + * + * @param errorCategory error category received + * @param bundle Bundle to populate + */ + private void populateErrorBundle(Bundle bundle, String errorCategory) { + + Bundle errorBundle = new Bundle(); + errorBundle.putString( + AuthenticationConstants.ERROR_CATEGORY, + errorCategory); + bundle.putBundle( + AuthenticationConstants.ERROR_BUNDLE, errorBundle); + } } diff --git a/PassThroughAdsComponent/src/main/java/com/amazon/ads/passthrough/PassThroughAds.java b/PassThroughAdsComponent/src/main/java/com/amazon/ads/passthrough/PassThroughAds.java index d5da9ef..b163045 100644 --- a/PassThroughAdsComponent/src/main/java/com/amazon/ads/passthrough/PassThroughAds.java +++ b/PassThroughAdsComponent/src/main/java/com/amazon/ads/passthrough/PassThroughAds.java @@ -78,6 +78,15 @@ public void setCurrentVideoPosition(double position) { } + /** + * {@inheritDoc} + */ + @Override + public boolean isPostRollAvailable() { + + return false; + } + /** * {@inheritDoc} */ diff --git a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CaptionStyleCompat.java b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CaptionStyleCompat.java deleted file mode 100644 index b61617e..0000000 --- a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CaptionStyleCompat.java +++ /dev/null @@ -1,183 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.amazon.android.uamp.constants; - -import android.annotation.TargetApi; -import android.graphics.Color; -import android.graphics.Typeface; -import android.view.accessibility.CaptioningManager; - -/** - * A compatibility wrapper for {@link CaptioningManager.CaptionStyle}. - */ -public final class CaptionStyleCompat { - - /** - * Edge type value specifying no character edges. - */ - private static final int EDGE_TYPE_NONE = 0; - - /** - * Edge type value specifying uniformly outlined character edges. - */ - public static final int EDGE_TYPE_OUTLINE = 1; - - /** - * Edge type value specifying drop-shadowed character edges. - */ - public static final int EDGE_TYPE_DROP_SHADOW = 2; - - /** - * Edge type value specifying raised bevel character edges. - */ - public static final int EDGE_TYPE_RAISED = 3; - - /** - * Edge type value specifying depressed bevel character edges. - */ - public static final int EDGE_TYPE_DEPRESSED = 4; - - /** - * Use color setting specified by the track and fallback to default caption style. - */ - public static final int USE_TRACK_COLOR_SETTINGS = 1; - - /** - * Default caption style. - */ - public static final CaptionStyleCompat DEFAULT = new CaptionStyleCompat( - Color.WHITE, Color.BLACK, Color.TRANSPARENT, EDGE_TYPE_NONE, Color.WHITE, null); - - /** - * The preferred foreground color. - */ - public final int foregroundColor; - - /** - * The preferred background color. - */ - public final int backgroundColor; - - /** - * The preferred window color. - */ - public final int windowColor; - - /** - * The preferred edge type. One of: - *
    - *
  • {@link #EDGE_TYPE_NONE} - *
  • {@link #EDGE_TYPE_OUTLINE} - *
  • {@link #EDGE_TYPE_DROP_SHADOW} - *
  • {@link #EDGE_TYPE_RAISED} - *
  • {@link #EDGE_TYPE_DEPRESSED} - *
- */ - public final int edgeType; - - /** - * The preferred edge color, if using an edge type other than {@link #EDGE_TYPE_NONE}. - */ - public final int edgeColor; - - /** - * The preferred typeface. - */ - public final Typeface typeface; - - private static final int SDK_INT = android.os.Build.VERSION.SDK_INT; - - /** - * Creates a {@link CaptionStyleCompat} equivalent to a provided {@link - * CaptioningManager.CaptionStyle}. - * - * @param captionStyle A {@link CaptioningManager.CaptionStyle}. - * @return The equivalent {@link CaptionStyleCompat}. - */ - @TargetApi(19) - public static CaptionStyleCompat createFromCaptionStyle( - CaptioningManager.CaptionStyle captionStyle) { - - if (SDK_INT >= 21) { - return createFromCaptionStyleV21(captionStyle); - } - else { - // Note - Any caller must be on at least API level 19 or greater (because - // CaptionStyle did - // not exist in earlier API levels). - return createFromCaptionStyleV19(captionStyle); - } - } - - /** - * @param foregroundColor See {@link #foregroundColor}. - * @param backgroundColor See {@link #backgroundColor}. - * @param windowColor See {@link #windowColor}. - * @param edgeType See {@link #edgeType}. - * @param edgeColor See {@link #edgeColor}. - * @param typeface See {@link #typeface}. - */ - public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, - int edgeType, int edgeColor, Typeface typeface) { - - this.foregroundColor = foregroundColor; - this.backgroundColor = backgroundColor; - this.windowColor = windowColor; - this.edgeType = edgeType; - this.edgeColor = edgeColor; - this.typeface = typeface; - } - - @TargetApi(19) - private static CaptionStyleCompat createFromCaptionStyleV19( - CaptioningManager.CaptionStyle captionStyle) { - - return new CaptionStyleCompat( - captionStyle.foregroundColor, captionStyle.backgroundColor, Color.TRANSPARENT, - captionStyle.edgeType, captionStyle.edgeColor, captionStyle.getTypeface()); - } - - @TargetApi(21) - private static CaptionStyleCompat createFromCaptionStyleV21( - CaptioningManager.CaptionStyle captionStyle) { - - return new CaptionStyleCompat( - captionStyle.hasForegroundColor() ? - captionStyle.foregroundColor : DEFAULT.foregroundColor, - captionStyle.hasBackgroundColor() ? - captionStyle.backgroundColor : DEFAULT.backgroundColor, - captionStyle.hasWindowColor() ? captionStyle.windowColor : DEFAULT.windowColor, - captionStyle.hasEdgeType() ? captionStyle.edgeType : DEFAULT.edgeType, - captionStyle.hasEdgeColor() ? captionStyle.edgeColor : DEFAULT.edgeColor, - captionStyle.getTypeface()); - } - -} diff --git a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CuePainter.java b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CuePainter.java deleted file mode 100644 index 0fcfbc0..0000000 --- a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/CuePainter.java +++ /dev/null @@ -1,394 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.amazon.android.uamp.textrenderer; - -import com.amazon.android.uamp.constants.CaptionStyleCompat; -import com.amazon.mediaplayer.playback.text.Cue; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; - -/** - * Draws {@link Cue}s. - */ -/* package */ final class CuePainter { - - private static final String TAG = "CuePainter"; - - /** - * Ratio of inner padding to font size. - */ - private static final float INNER_PADDING_RATIO = 0.125f; - - /** - * Temporary rectangle used for computing line bounds. - */ - private final RectF lineBounds = new RectF(); - - // Styled dimensions. - private final float cornerRadius; - private final float outlineWidth; - private final float shadowRadius; - private final float shadowOffset; - private final float spacingMult; - private final float spacingAdd; - - private final TextPaint textPaint; - private final Paint paint; - - // Previous input variables. - private CharSequence cueText; - private Layout.Alignment cueTextAlignment; - private float cueLine; - private int cueLineType; - private int cueLineAnchor; - private float cuePosition; - private int cuePositionAnchor; - private float cueSize; - private boolean applyEmbeddedStyles; - private int foregroundColor; - private int backgroundColor; - private int windowColor; - private int edgeColor; - private int edgeType; - private float textSizePx; - private float bottomPaddingFraction; - private int parentLeft; - private int parentTop; - private int parentRight; - private int parentBottom; - - // Derived drawing variables. - private StaticLayout textLayout; - private int textLeft; - private int textTop; - private int textPaddingX; - - private static boolean areEqual(Object o1, Object o2) { - - return o1 == null ? o2 == null : o1.equals(o2); - } - - /** - * Constructor - * - * @param context application context - */ - public CuePainter(Context context) { - - int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; - TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); - spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); - spacingMult = styledAttributes.getFloat(1, 1); - styledAttributes.recycle(); - - Resources resources = context.getResources(); - DisplayMetrics displayMetrics = resources.getDisplayMetrics(); - int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics - .DENSITY_DEFAULT); - cornerRadius = twoDpInPx; - outlineWidth = twoDpInPx; - shadowRadius = twoDpInPx; - shadowOffset = twoDpInPx; - - textPaint = new TextPaint(); - textPaint.setAntiAlias(true); - textPaint.setSubpixelText(true); - - paint = new Paint(); - paint.setAntiAlias(true); - paint.setStyle(Paint.Style.FILL); - } - - /** - * Draws the provided {@link Cue} into a canvas with the specified styling. - *

- * A call to this method is able to use cached results of calculations made during the previous - * call, and so an instance of this class is able to optimize repeated calls to this method in - * which the same parameters are passed. - * - * @param cue The cue to draw. - * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. - * @param style The style to use when drawing the cue text. - * @param textSizePx The text size to use when drawing the cue text, in pixels. - * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is - * {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height - * @param canvas The canvas into which to draw. - * @param cueBoxLeft The left position of the enclosing cue box. - * @param cueBoxTop The top position of the enclosing cue box. - * @param cueBoxRight The right position of the enclosing cue box. - * @param cueBoxBottom The bottom position of the enclosing cue box. - */ - public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float - textSizePx, - float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, - int cueBoxRight, - int cueBoxBottom) { - - CharSequence cueText = cue.text; - if (TextUtils.isEmpty(cueText)) { - // Nothing to draw. - return; - } - if (!applyEmbeddedStyles) { - // Strip out any embedded styling. - cueText = cueText.toString(); - } - if (areCharSequencesEqual(this.cueText, cueText) - && areEqual(this.cueTextAlignment, cue.textAlignment) - && this.cueLine == cue.line - && this.cueLineType == cue.lineType - && areEqual(this.cueLineAnchor, cue.lineAnchor) - && this.cuePosition == cue.position - && areEqual(this.cuePositionAnchor, cue.positionAnchor) - && this.cueSize == cue.size - && this.applyEmbeddedStyles == applyEmbeddedStyles - && this.foregroundColor == style.foregroundColor - && this.backgroundColor == style.backgroundColor - && this.windowColor == style.windowColor - && this.edgeType == style.edgeType - && this.edgeColor == style.edgeColor - && areEqual(this.textPaint.getTypeface(), style.typeface) - && this.textSizePx == textSizePx - && this.bottomPaddingFraction == bottomPaddingFraction - && this.parentLeft == cueBoxLeft - && this.parentTop == cueBoxTop - && this.parentRight == cueBoxRight - && this.parentBottom == cueBoxBottom) { - // We can use the cached layout. - drawLayout(canvas); - return; - } - - this.cueText = cueText; - this.cueTextAlignment = cue.textAlignment; - this.cueLine = cue.line; - this.cueLineType = cue.lineType; - this.cueLineAnchor = cue.lineAnchor; - this.cuePosition = cue.position; - this.cuePositionAnchor = cue.positionAnchor; - this.cueSize = cue.size; - this.applyEmbeddedStyles = applyEmbeddedStyles; - this.foregroundColor = style.foregroundColor; - this.backgroundColor = style.backgroundColor; - this.windowColor = style.windowColor; - this.edgeType = style.edgeType; - this.edgeColor = style.edgeColor; - this.textPaint.setTypeface(style.typeface); - this.textSizePx = textSizePx; - this.bottomPaddingFraction = bottomPaddingFraction; - this.parentLeft = cueBoxLeft; - this.parentTop = cueBoxTop; - this.parentRight = cueBoxRight; - this.parentBottom = cueBoxBottom; - - int parentWidth = parentRight - parentLeft; - int parentHeight = parentBottom - parentTop; - - textPaint.setTextSize(textSizePx); - int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f); - - int availableWidth = parentWidth - textPaddingX * 2; - if (cueSize != Cue.DIMEN_UNSET) { - availableWidth = (int) (availableWidth * cueSize); - } - if (availableWidth <= 0) { - Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)"); - return; - } - - Layout.Alignment textAlignment = cueTextAlignment == null ? Layout.Alignment. - ALIGN_CENTER : cueTextAlignment; - textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, - spacingMult, - spacingAdd, true); - int textHeight = textLayout.getHeight(); - int textWidth = 0; - int lineCount = textLayout.getLineCount(); - for (int i = 0; i < lineCount; i++) { - textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth); - } - textWidth += textPaddingX * 2; - - int textLeft; - int textRight; - if (cuePosition != Cue.DIMEN_UNSET) { - int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; - textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth - : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - - textWidth) / 2 - : anchorPosition; - textLeft = Math.max(textLeft, parentLeft); - textRight = Math.min(textLeft + textWidth, parentRight); - } - else { - textLeft = (parentWidth - textWidth) / 2; - textRight = textLeft + textWidth; - } - - int textTop; - int textBottom; - if (cueLine != Cue.DIMEN_UNSET) { - int anchorPosition; - if (cueLineType == Cue.LINE_TYPE_FRACTION) { - anchorPosition = Math.round(parentHeight * cueLine) + parentTop; - } - else { - // cueLineType == Cue.LINE_TYPE_NUMBER - int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0); - if (cueLine >= 0) { - anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; - } - else { - anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom; - } - } - textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight - : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) - / 2 - : anchorPosition; - textBottom = textTop + textHeight; - if (textBottom > parentBottom) { - textTop = parentBottom - textHeight; - textBottom = parentBottom; - } - else if (textTop < parentTop) { - textTop = parentTop; - textBottom = parentTop + textHeight; - } - } - else { - textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction); - textBottom = textTop + textHeight; - } - - textWidth = textRight - textLeft; - - // Update the derived drawing variables. - this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, - spacingMult, - spacingAdd, true); - this.textLeft = textLeft; - this.textTop = textTop; - this.textPaddingX = textPaddingX; - - drawLayout(canvas); - } - - /** - * Draws {@link #textLayout} into the provided canvas. - * - * @param canvas The canvas into which to draw. - */ - private void drawLayout(Canvas canvas) { - - final StaticLayout layout = textLayout; - if (layout == null) { - // Nothing to draw. - return; - } - - int saveCount = canvas.save(); - canvas.translate(textLeft, textTop); - - if (Color.alpha(windowColor) > 0) { - paint.setColor(windowColor); - canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(), - paint); - } - - if (Color.alpha(backgroundColor) > 0) { - paint.setColor(backgroundColor); - float previousBottom = layout.getLineTop(0); - int lineCount = layout.getLineCount(); - for (int i = 0; i < lineCount; i++) { - lineBounds.left = layout.getLineLeft(i) - textPaddingX; - lineBounds.right = layout.getLineRight(i) + textPaddingX; - lineBounds.top = previousBottom; - lineBounds.bottom = layout.getLineBottom(i); - previousBottom = lineBounds.bottom; - canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint); - } - } - - if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { - textPaint.setStrokeJoin(Paint.Join.ROUND); - textPaint.setStrokeWidth(outlineWidth); - textPaint.setColor(edgeColor); - textPaint.setStyle(Paint.Style.FILL_AND_STROKE); - layout.draw(canvas); - } - else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { - textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor); - } - else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED - || edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { - boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; - int colorUp = raised ? Color.WHITE : edgeColor; - int colorDown = raised ? edgeColor : Color.WHITE; - float offset = shadowRadius / 2f; - textPaint.setColor(foregroundColor); - textPaint.setStyle(Paint.Style.FILL); - textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp); - layout.draw(canvas); - textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown); - } - - textPaint.setColor(foregroundColor); - textPaint.setStyle(Paint.Style.FILL); - layout.draw(canvas); - textPaint.setShadowLayer(0, 0, 0, 0); - - canvas.restoreToCount(saveCount); - } - - /** - * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because - * the latter only checks the text of each sequence, and does not check for equality of styling - * that may be embedded within the {@link CharSequence}s. - */ - private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { - // Some CharSequence implementations don't perform a cheap referential equality check in - // their equals methods, so we perform one explicitly here. - return first == second || (first != null && first.equals(second)); - } -} diff --git a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/SubtitleLayout.java b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/SubtitleLayout.java deleted file mode 100644 index ef6cb08..0000000 --- a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/textrenderer/SubtitleLayout.java +++ /dev/null @@ -1,269 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.amazon.android.uamp.textrenderer; - -import com.amazon.android.uamp.constants.CaptionStyleCompat; -import com.amazon.mediaplayer.playback.text.Cue; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; - -import java.util.ArrayList; -import java.util.List; - -/** - * A view for rendering rich-formatted captions. - */ -public final class SubtitleLayout extends View { - - /** - * The default fractional text size. - * - * @see #setFractionalTextSize(float, boolean) - */ - private static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f; - - /** - * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a - * fraction of the viewport height. - * - * @see #setBottomPaddingFraction(float) - */ - private static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; - - private static final int FRACTIONAL = 0; - private static final int FRACTIONAL_IGNORE_PADDING = 1; - private static final int ABSOLUTE = 2; - - private final List painters; - - private List cues; - private int textSizeType; - private float textSize; - private boolean applyEmbeddedStyles; - private CaptionStyleCompat style; - private float bottomPaddingFraction; - - /** - * Constructor - * - * @param context Application context. - */ - public SubtitleLayout(Context context) { - - this(context, null); - } - - /** - * Constructor - * - * @param context Application context. - * @param attrs Attribute sets. - */ - public SubtitleLayout(Context context, AttributeSet attrs) { - - super(context, attrs); - painters = new ArrayList<>(); - textSizeType = FRACTIONAL; - textSize = DEFAULT_TEXT_SIZE_FRACTION; - applyEmbeddedStyles = true; - style = CaptionStyleCompat.DEFAULT; - bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; - } - - /** - * Sets the cues to be displayed by the view. - * - * @param cues The cues to display. - */ - public void setCues(List cues) { - - if (this.cues == cues) { - return; - } - this.cues = cues; - // Ensure we have sufficient painters. - int cueCount = (cues == null) ? 0 : cues.size(); - while (painters.size() < cueCount) { - painters.add(new CuePainter(getContext())); - } - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Set the text size to a given unit and value. - *

- * See {@link TypedValue} for the possible dimension units. - * - * @param unit The desired dimension unit. - * @param size The desired size in the given units. - */ - public void setFixedTextSize(int unit, float size) { - - Context context = getContext(); - Resources resources; - if (context == null) { - resources = Resources.getSystem(); - } - else { - resources = context.getResources(); - } - setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); - } - - /** - * Sets the text size to be a fraction of the view's remaining height after its top and bottom - * padding have been subtracted. - *

- * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}. - * - * @param fractionOfHeight A fraction between 0 and 1. - */ - public void setFractionalTextSize(float fractionOfHeight) { - - setFractionalTextSize(fractionOfHeight, false); - } - - /** - * Sets the text size to be a fraction of the height of this view. - * - * @param fractionOfHeight A fraction between 0 and 1. - * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a - * fraction of this view's height ignoring any top and bottom padding. - * Set to false if - * {@code fractionOfHeight} should be interpreted as a fraction of this - * view's remaining - * height after the top and bottom padding has been subtracted. - */ - private void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) { - - setTextSize(ignorePadding ? FRACTIONAL_IGNORE_PADDING : FRACTIONAL, fractionOfHeight); - } - - private void setTextSize(int textSizeType, float textSize) { - - if (this.textSizeType == textSizeType && this.textSize == textSize) { - return; - } - this.textSizeType = textSizeType; - this.textSize = textSize; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets whether styling embedded within the cues should be applied. Enabled by default. - * - * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. - */ - public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { - - if (this.applyEmbeddedStyles == applyEmbeddedStyles) { - return; - } - this.applyEmbeddedStyles = applyEmbeddedStyles; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets the caption style. - * - * @param style A style for the view. - */ - public void setStyle(CaptionStyleCompat style) { - - if (this.style == style) { - return; - } - this.style = style; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, - * as a fraction of the view's remaining height after its top and bottom padding have been - * subtracted. - *

- * Note that this padding is applied in addition to any standard view padding. - * - * @param bottomPaddingFraction The bottom padding fraction. - */ - public void setBottomPaddingFraction(float bottomPaddingFraction) { - - if (this.bottomPaddingFraction == bottomPaddingFraction) { - return; - } - this.bottomPaddingFraction = bottomPaddingFraction; - // Invalidate to trigger drawing. - invalidate(); - } - - /** - * {@inheritDoc} - */ - @Override - public void dispatchDraw(Canvas canvas) { - - int cueCount = (cues == null) ? 0 : cues.size(); - int rawTop = getTop(); - int rawBottom = getBottom(); - - // Calculate the bounds after padding is taken into account. - int left = getLeft() + getPaddingLeft(); - int top = rawTop + getPaddingTop(); - int right = getRight() + getPaddingRight(); - int bottom = rawBottom - getPaddingBottom(); - if (bottom <= top || right <= left) { - // No space to draw subtitles. - return; - } - - float textSizePx = textSizeType == ABSOLUTE ? textSize - : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); - if (textSizePx <= 0) { - // Text has no height. - return; - } - - for (int i = 0; i < cueCount; i++) { - painters.get(i).draw(cues.get(i), applyEmbeddedStyles, style, textSizePx, - bottomPaddingFraction, canvas, left, top, right, bottom); - } - } - -} diff --git a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackActivity.java b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackActivity.java index f87adb2..de5355a 100755 --- a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackActivity.java +++ b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackActivity.java @@ -30,24 +30,23 @@ package com.amazon.android.uamp.ui; +import com.google.android.exoplayer.text.SubtitleLayout; + import com.amazon.ads.IAds; +import com.amazon.ads.AdMetaData; import com.amazon.analytics.AnalyticsTags; import com.amazon.android.contentbrowser.ContentBrowser; import com.amazon.android.contentbrowser.database.ContentDatabaseHelper; import com.amazon.android.contentbrowser.database.RecentRecord; import com.amazon.android.contentbrowser.helper.AnalyticsHelper; -import com.amazon.android.contentbrowser.helper.AuthHelper; import com.amazon.android.model.content.Content; import com.amazon.android.module.ModuleManager; -import com.amazon.android.navigator.Navigator; -import com.amazon.android.navigator.UINode; import com.amazon.android.recipe.Recipe; import com.amazon.android.tv.tenfoot.R; import com.amazon.android.uamp.DrmProvider; import com.amazon.android.uamp.UAMP; import com.amazon.android.uamp.constants.PreferencesConstants; -import com.amazon.android.uamp.textrenderer.SubtitleLayout; import com.amazon.android.ui.fragments.ErrorDialogFragment; import com.amazon.android.utils.ErrorUtils; import com.amazon.android.utils.Helpers; @@ -78,6 +77,7 @@ import android.widget.FrameLayout; import android.widget.ProgressBar; +import java.util.ArrayList; import java.util.List; import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper; @@ -138,9 +138,14 @@ public class PlaybackActivity extends Activity implements private boolean mIsCloseCaptionEnabled = false; /** - * Is Content support close caption flag. + * Is Content support outband close caption flag. + */ + private boolean mHasOutbandCC = false; + + /** + * Is activity onResume is being received at activity created. */ - private boolean mIsContentSupportCC = false; + private boolean resumeOnCreation = false; enum AudioFocusState { Focused, @@ -176,6 +181,8 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + //flag for onResume to know this is being called at activity creation + resumeOnCreation = true; // Create video position tracking handler. mVideoPositionTrackingHandler = new Handler(); @@ -258,9 +265,9 @@ protected void onStart() { } /** - * resume Playback Activity if user Authentication is success + * resume Playback Activity */ - private void resumePlaybackAfterAuthentication() { + private void resumePlayback() { // Start tracking video position changes. mVideoPositionTrackingHandler.post(mVideoPositionTrackingRunnable); @@ -305,61 +312,12 @@ private void resumePlaybackAfterAuthentication() { break; } - long duration = getDuration(); - // Duration wasn't found using the player, try getting it directly from the content. - if (duration == 0) { - duration = mSelectedContent.getDuration(); - } - AnalyticsHelper.trackPlaybackStarted(mSelectedContent, duration, mCurrentPlaybackPosition); - // Let ads implementation track player activity lifecycle. if (mAdsImplementation != null) { mAdsImplementation.setActivityState(IAds.ActivityState.RESUME); } } - /** - * Authenticate User On Resume of PlayBackActivity. - * - * @param screenName Screen name - */ - private void authenticateUserOnResume(String screenName) { - - Navigator mNavigator = ContentBrowser.getInstance(this).getNavigator(); - AuthHelper mAuthHelper = ContentBrowser.getInstance(this).getAuthHelper(); - UINode uiNode = (UINode) mNavigator.getNodeObjectByScreenName(screenName); - Log.d(TAG, "AuthenticateUserOnResume called in:" + screenName); - Log.d(TAG, "AuthenticateUserOnResume needed:" + uiNode.isVerifyScreenAccess()); - //check if this Screen need Access verification - if (uiNode.isVerifyScreenAccess()) { - boolean loginLater = Preferences.getBoolean(AuthHelper.LOGIN_LATER_PREFERENCES_KEY); - //Check if Authentication can be deferred or not - if (!mAuthHelper.getIAuthentication().isAuthenticationCanBeDoneLater() || - (!loginLater && mAuthHelper.getIAuthentication() - .isAuthenticationCanBeDoneLater())) { - //Check if user is Authenticated for the content - mAuthHelper.isAuthenticated().subscribe(extras -> { - if (extras.getBoolean(AuthHelper.RESULT)) { - //Playback activity need to be resumed as Authentication succeeded. - resumePlaybackAfterAuthentication(); - } - else { - //Playback activity need to be closed as Authentication failed. - finish(); - Log.i(TAG, "Traversing to details page since user is not authenticated " + - "any more"); - } - }); - } - else { - resumePlaybackAfterAuthentication(); - } - } - else { - resumePlaybackAfterAuthentication(); - } - } - /** * {@inheritDoc} */ @@ -367,9 +325,22 @@ private void authenticateUserOnResume(String screenName) { public void onResume() { super.onResume(); + // TODO: Move this handling from here to content browser after refactoring + // VerifyScreenSwitch DEVTECH-4038 + //Check before resume as user might not authenticated any more. One such scenario is // coming back from next/prev screen when user account has been disabled from server. - authenticateUserOnResume(ContentBrowser.CONTENT_RENDERER_SCREEN); + if (resumeOnCreation) { + resumeOnCreation = false; + resumePlayback(); + } + else { + ContentBrowser.getInstance(this).verifyScreenSwitch(ContentBrowser + .CONTENT_RENDERER_SCREEN, + extra -> + resumePlayback(), + errorExtra -> finish()); + } } /** @@ -581,6 +552,10 @@ public int getDuration() { duration = 0; } } + // Duration wasn't found using the player, try getting it directly from the content. + if (duration == 0 && mSelectedContent != null) { + duration = mSelectedContent.getDuration(); + } return (int) duration; } @@ -660,9 +635,6 @@ public void changeContent(Content content) { loadContentPlaybackState(); mStartingPlaybackPosition = mCurrentPlaybackPosition; - // User will start watching this new content so track it with analytics. - AnalyticsHelper.trackPlaybackStarted(mSelectedContent, mStartingPlaybackPosition, - mCurrentPlaybackPosition); if (mPlayer != null) { mPlayer.close(); @@ -763,18 +735,31 @@ public void onFragmentFfwRwd(int position) { @Override public void onCloseCaptionButtonStateChanged(boolean state) { + modifyClosedCaptionState(state); + } + + public void modifyClosedCaptionState(boolean state) { + if (mPlayer != null && mPlaybackOverlayFragment != null) { - if (mIsContentSupportCC) { - // Enable CC. - mPlayer.enableTextTrack(TrackType.SUBTITLE, state); + if (isClosedCaptionAvailable()) { + + // Enable CC. Prioritizing CLOSED_CAPTION before SUBTITLE if enabled + if (ContentBrowser.getInstance(this).isEnableCEA608() && + mPlayer.getTrackCount(TrackType.CLOSED_CAPTION) > 0) { + mPlayer.enableTextTrack(TrackType.CLOSED_CAPTION, state); + } + else { + mPlayer.enableTextTrack(TrackType.SUBTITLE, state); + } + // Update internal state. mIsCloseCaptionEnabled = state; - mPlaybackOverlayFragment.updateCCButtonState(state, mIsContentSupportCC); + mPlaybackOverlayFragment.updateCCButtonState(state, true); Log.d(TAG, "Content support CC. Change CC state to " + state); } else { // Disable CC button back. - mPlaybackOverlayFragment.updateCCButtonState(false, mIsContentSupportCC); + mPlaybackOverlayFragment.updateCCButtonState(false, false); // Do not disable mIsCloseCaptionEnabled as we want it persistent. Log.d(TAG, "Content does not support CC. Change CC state to false"); } @@ -822,7 +807,7 @@ private void setVisibilityOfViewGroupWithInnerSurfaceView(ViewGroup viewGroup, i } } - private void switch2VideoView() { + private void switchToVideoView() { // Show Video view. setVisibilityOfViewGroupWithInnerSurfaceView(mVideoView, View.VISIBLE); // Show Subtitle view. @@ -831,7 +816,7 @@ private void switch2VideoView() { setVisibilityOfViewGroupWithInnerSurfaceView(mAdsView, View.GONE); } - private void switch2AdsView() { + private void switchToAdsView() { // Show Ads view. setVisibilityOfViewGroupWithInnerSurfaceView(mAdsView, View.VISIBLE); // Hide Video view. @@ -1097,8 +1082,45 @@ private void clearPlayerCallbacks() { */ @Override public void onCues(List cues) { + // Unfortunately need to convert again as interface is player agnostic. + final List convertedCues = new ArrayList<>(); + com.google.android.exoplayer.text.Cue aCue; + for (com.amazon.mediaplayer.playback.text.Cue cue : cues) { + aCue = new com.google.android.exoplayer.text.Cue(cue.text, + cue.textAlignment, + cue.line, + cue.lineType, + cue.lineAnchor, + cue.position, + cue.positionAnchor, + cue.size); + convertedCues.add(aCue); + } + mSubtitleLayout.setCues(convertedCues); + } + + /** + * Create and return AdMetaData object from extras received from ad module, + * consumer of this object is Analytics module + * + * @param extras Bundle containing ad metadata + * @return POJO with ad metadata + */ + private AdMetaData getAdAnalyticsData(Bundle extras) { - mSubtitleLayout.setCues(cues); + AdMetaData adMetaData = new AdMetaData(); + if (extras != null) { + if (extras.getString(IAds.ID) != null) { + adMetaData.setAdId(extras.getString(IAds.ID)); + } + if (extras.getString(IAds.AD_TYPE) != null) { + adMetaData.setAdType(extras.getString(IAds.AD_TYPE)); + } + adMetaData.setDurationReceived(extras.getLong(IAds.DURATION_RECEIVED)); + adMetaData.setDurationPlayed(extras.getLong(IAds.DURATION_PLAYED)); + Log.d(TAG, "Ad details: " + adMetaData.toString()); + } + return adMetaData; } private IAds.IAdsEvents mIAdsEvents = new IAds.IAdsEvents() { @@ -1106,7 +1128,7 @@ public void onCues(List cues) { public void onAdSlotStarted(Bundle extras) { Log.d(TAG, "onAdSlotStarted"); - AnalyticsHelper.trackAdStarted(mSelectedContent, getCurrentPosition()); + // Hide media controller. if (mPlaybackOverlayFragment != null && mPlaybackOverlayFragment.getView() != null) { mPlaybackOverlayFragment.getView().setVisibility(View.INVISIBLE); @@ -1115,51 +1137,66 @@ public void onAdSlotStarted(Bundle extras) { // Pause the video if we are already playing a content. if (mPlayer != null && isPlaying()) { mPlayer.pause(); + mCurrentPlaybackPosition = getCurrentPosition(); + AnalyticsHelper.trackPlaybackFinished(mSelectedContent, mStartingPlaybackPosition, + mCurrentPlaybackPosition); + mStartingPlaybackPosition = mCurrentPlaybackPosition; } - // Hide progress bar. hideProgress(); - // Show Ads view. - switch2AdsView(); + AnalyticsHelper.trackAdStarted(mSelectedContent, getCurrentPosition(), + getAdAnalyticsData(extras)); + switchToAdsView(); } @Override public void onAdSlotEnded(final Bundle extras) { Log.d(TAG, "onAdSlotEnded"); - // Show progress - showProgress(); - // Show video View. - switch2VideoView(); - // Will be used for Analytics. - long duration = 0; + // If the extra doesn't exist then we'll treat this with a default value of true. + boolean adPodComplete = extras == null || !extras.containsKey(IAds.AD_POD_COMPLETE) || + extras.getBoolean(IAds.AD_POD_COMPLETE); + + // We only want to show the video view if the group of ads has finished playing. + if (adPodComplete) { + showProgress(); + switchToVideoView(); + } + + AnalyticsHelper.trackAdEnded(mSelectedContent, getCurrentPosition(), + getAdAnalyticsData(extras)); + + String adType = null; if (extras != null) { - duration = extras.getLong(IAds.DURATION); - boolean wasAMidRoll = extras.getBoolean(IAds.WAS_A_MID_ROLL); - if (wasAMidRoll) { + adType = extras.getString(IAds.AD_TYPE); + } + + if (adType != null && adType.equals(IAds.MID_ROLL_AD)) { + + // Show the playback overlay and continue playing the video if the group of + // ads has finished playing. + if (adPodComplete) { // Show media controller. - if (mPlaybackOverlayFragment != null && mPlaybackOverlayFragment.getView() != - null) { + if (mPlaybackOverlayFragment != null + && mPlaybackOverlayFragment.getView() != null) { mPlaybackOverlayFragment.getView().setVisibility(View.VISIBLE); } // Resume movie after a mid roll. mPlayer.play(); } - else { - // Open Movie. - openContentHelper(mSelectedContent); - } - - Log.d(TAG, "Ad Played for " + duration + " ms"); + } + // If we came back from a post-roll ad that means the content is finished. + else if (adType != null && adType.equals(IAds.POST_ROLL_AD)) { + playbackFinished(); } else { // Open Movie. openContentHelper(mSelectedContent); } - AnalyticsHelper.trackAdEnded(mSelectedContent, duration, getCurrentPosition()); } + }; /** @@ -1211,7 +1248,7 @@ private void openContentHelper(Content content) { } } - mIsContentSupportCC = false; + mHasOutbandCC = false; AMZNMediaPlayer.TextMimeType ccType = AMZNMediaPlayer.TextMimeType.TEXT_WTT; List closeCaptionUrls = content.getCloseCaptionUrls(); @@ -1232,12 +1269,12 @@ private void openContentHelper(Content content) { if (lastDot > 0) { String ext = closeCaptionUrl.substring(lastDot + 1); if (ext.equals("vtt")) { - mIsContentSupportCC = true; + mHasOutbandCC = true; ccType = AMZNMediaPlayer.TextMimeType.TEXT_WTT; Log.d(TAG, "Close captioning is enabled & its format is TextWTT"); } else if (ext.equals("xml")) { - mIsContentSupportCC = true; + mHasOutbandCC = true; ccType = AMZNMediaPlayer.TextMimeType.TEXT_TTML; Log.d(TAG, "Close captioning is enabled & its format is TextTTML"); } @@ -1245,9 +1282,6 @@ else if (ext.equals("xml")) { } if (mPlaybackOverlayFragment != null) { - mPlaybackOverlayFragment.updateCCButtonState(mIsContentSupportCC && - mIsCloseCaptionEnabled, - mIsContentSupportCC); mPlaybackOverlayFragment.updateCurrentContent(mSelectedContent); } @@ -1259,16 +1293,15 @@ else if (ext.equals("xml")) { contentParameters.laurl = drmProvider.fetchLaUrl(); contentParameters.encryptionSchema = getAmznMediaEncryptionSchema(drmProvider); - if (mIsContentSupportCC) { - + if (mHasOutbandCC) { contentParameters.oobTextSources = new AMZNMediaPlayer.OutOfBandTextSource[]{new AMZNMediaPlayer.OutOfBandTextSource(closeCaptionUrl, ccType, "en")}; mPlayer.open(contentParameters); - Log.d(TAG, "Media player opened with close captioning support"); + Log.d(TAG, "Media player opened with outband close captioning support"); } else { mPlayer.open(contentParameters); - Log.d(TAG, "Media player opened without close captioning support"); + Log.d(TAG, "Media player opened without outband close captioning support"); } } @@ -1302,7 +1335,7 @@ private void openSelectedContent() { mAdsImplementation.setIAdsEvents(mIAdsEvents); // Hide videoView which make adsView visible. - switch2AdsView(); + switchToAdsView(); // Hide media controller. if (mPlaybackOverlayFragment != null && mPlaybackOverlayFragment.getView() != null) { @@ -1374,15 +1407,6 @@ public void onPlayerStateChange(PlayerState oldState, PlayerState newState, Bund else { mIsContentChangeRequested = false; } - // Remember CC state for playbacks. - if (mIsContentSupportCC && mIsCloseCaptionEnabled) { - if (mPlaybackOverlayFragment != null) { - mPlaybackOverlayFragment.updateCCButtonState(true, true); - } - if (mPlayer != null) { - mPlayer.enableTextTrack(TrackType.SUBTITLE, true); - } - } break; case PREPARING: // Show media controller. @@ -1400,6 +1424,9 @@ public void onPlayerStateChange(PlayerState oldState, PlayerState newState, Bund mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); if (mPrevState == PlayerState.PREPARING) { if (mPlaybackOverlayFragment != null) { + // Remember CC state for playbacks. + modifyClosedCaptionState(mIsCloseCaptionEnabled); + mPlaybackOverlayFragment.updatePlayback(); mPlaybackOverlayFragment.startProgressAutomation(); } @@ -1437,6 +1464,11 @@ else if (mAudioFocusState == AudioFocusState.NoFocusNoDuck) { if (mAdsImplementation != null) { mAdsImplementation.setPlayerState(IAds.PlayerState.PLAYING); } + if (mCurrentPlaybackPosition < getCurrentPosition()) { + mCurrentPlaybackPosition = getCurrentPosition(); + } + AnalyticsHelper.trackPlaybackStarted(mSelectedContent, getDuration(), + mCurrentPlaybackPosition); break; case BUFFERING: showProgress(); @@ -1449,15 +1481,15 @@ else if (mAudioFocusState == AudioFocusState.NoFocusNoDuck) { break; case ENDED: hideProgress(); - if (mPlaybackOverlayFragment != null) { - mPlaybackOverlayFragment.playbackFinished(); - } - mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - // Let ads implementation track player state. - if (mAdsImplementation != null) { + // Let ads implementation track player state and check for any post-roll ads. + if (mAdsImplementation != null && mAdsImplementation.isPostRollAvailable()) { mAdsImplementation.setPlayerState(IAds.PlayerState.COMPLETED); } + // If there's no ads just directly finish playback. + else { + playbackFinished(); + } + break; case CLOSING: if (mPlaybackOverlayFragment != null) { @@ -1475,6 +1507,18 @@ else if (mAudioFocusState == AudioFocusState.NoFocusNoDuck) { } } + /** + * Private helper method to do some cleanup when playback has finished. + */ + private void playbackFinished() { + + if (mPlaybackOverlayFragment != null) { + mPlaybackOverlayFragment.playbackFinished(); + } + mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + /** * {@inheritDoc} */ @@ -1603,4 +1647,20 @@ public void doButtonClick(ErrorDialogFragment errorDialogFragment, ErrorUtils } } + + public boolean isClosedCaptionAvailable() { + + if (mPlayer.getTrackCount(TrackType.SUBTITLE) > 0) { + Log.d(TAG, "Subtitle Tracks Available: " + mPlayer.getTrackCount(TrackType.SUBTITLE)); + return true; + } + else if (ContentBrowser.getInstance(this).isEnableCEA608() && + mPlayer.getTrackCount(TrackType.CLOSED_CAPTION) > 0) { + Log.d(TAG, "Closed Caption Tracks Available: " + mPlayer.getTrackCount(TrackType.CLOSED_CAPTION)); + return true; + } + else { + return false; + } + } } diff --git a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackOverlayFragment.java b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackOverlayFragment.java index de6f2d1..c3a5684 100755 --- a/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackOverlayFragment.java +++ b/TVUIComponent/lib/src/main/java/com/amazon/android/uamp/ui/PlaybackOverlayFragment.java @@ -370,7 +370,9 @@ else if (action.getId() == mSkipNextAction.getId()) { trackAnalyticsAction(AnalyticsTags.ACTION_PLAYBACK_CONTROL_NEXT); ContentBrowser.getInstance(getActivity()).verifyScreenSwitch(ContentBrowser .CONTENT_RENDERER_SCREEN, extra -> - next()); + next(), + errorExtra -> + ContentBrowser.getInstance(getActivity()).showAuthenticationErrorDialog(errorExtra)); } else if (action.getId() == mClosedCaptioningAction.getId()) { toggleCloseCaption(); @@ -380,7 +382,9 @@ else if (action.getId() == mSkipPreviousAction.getId()) { trackAnalyticsAction(AnalyticsTags.ACTION_PLAYBACK_CONTROL_PRE); ContentBrowser.getInstance(getActivity()).verifyScreenSwitch(ContentBrowser .CONTENT_RENDERER_SCREEN, extra -> - prev()); + prev(), + errorExtra -> + ContentBrowser.getInstance(getActivity()).showAuthenticationErrorDialog(errorExtra)); } else if (action.getId() == mFastForwardAction.getId()) { fastForward(); @@ -940,7 +944,10 @@ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, ContentBrowser.getInstance(getActivity()).verifyScreenSwitch(ContentBrowser .CONTENT_RENDERER_SCREEN, extra -> - mCallback.changeContent(content)); + mCallback + .changeContent(content), + errorExtra -> + ContentBrowser.getInstance(getActivity()).showAuthenticationErrorDialog(errorExtra)); } } } diff --git a/UAMP/build.gradle b/UAMP/build.gradle index 93afc46..0c99570 100644 --- a/UAMP/build.gradle +++ b/UAMP/build.gradle @@ -48,6 +48,8 @@ jacoco { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) + compile files('libs/exoplayer.jar') + compile 'com.android.support:recyclerview-v7:23.1.1' compile 'com.android.support:leanback-v17:23.1.1' compile 'com.android.support:appcompat-v7:23.1.1' diff --git a/AMZNMediaPlayerComponent/libs/exoplayer.jar b/UAMP/libs/exoplayer.jar similarity index 100% rename from AMZNMediaPlayerComponent/libs/exoplayer.jar rename to UAMP/libs/exoplayer.jar diff --git a/UAMP/src/main/res/layout/playback_controls.xml b/UAMP/src/main/res/layout/playback_controls.xml index 88ee779..115a468 100644 --- a/UAMP/src/main/res/layout/playback_controls.xml +++ b/UAMP/src/main/res/layout/playback_controls.xml @@ -46,7 +46,7 @@ permissions and limitations under the License. android:layout_gravity="center" android:layout_centerInParent="true"> - diff --git a/Utils/src/androidTest/assets/LoadArrayMappingTest1.json b/Utils/src/androidTest/assets/LoadArrayMappingTest1.json new file mode 100644 index 0000000..c76ad88 --- /dev/null +++ b/Utils/src/androidTest/assets/LoadArrayMappingTest1.json @@ -0,0 +1,9 @@ +{ + "strings":["value1", "value2"], + "integers": [4, 7, 13], + "booleans": [true, false], + "emptyObjects": [{} , {}], + "emptyLists": [[],[], []], + "objects": [{"innerKey1":"innerValue1"}, {"innerKey2":"innerValue2"}, {"innerKey3":"innerValue3"}], + "lists": [[1, 2, 3], [4, 5, 6], [13, 16, 20]] +} \ No newline at end of file diff --git a/Utils/src/androidTest/java/com/amazon/android/utils/HelpersTest.java b/Utils/src/androidTest/java/com/amazon/android/utils/HelpersTest.java deleted file mode 100644 index 87eadee..0000000 --- a/Utils/src/androidTest/java/com/amazon/android/utils/HelpersTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -package com.amazon.android.utils; - - -import com.amazon.utils.test.R; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.util.Log; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import java.util.HashMap; -import java.util.Map; - -/** - * Tests the {@link Helpers} class. - */ -@RunWith(AndroidJUnit4.class) - -public class HelpersTest { - - private static final String TAG = HelpersTest.class.getSimpleName(); - - /** - * Tests the {@link Helpers#loadStringMappingFromJsonFile(Context, int)} method for the - * positive case. - */ - @Test - public void testLoadStringMappingFromJsonFilePositive() { - - HashMap result = - Helpers.loadStringMappingFromJsonFile(InstrumentationRegistry.getContext(), - R.string.load_string_mapping_test1_file); - - assertEquals("There should be 4 key-value pairs in the map", 7, result.size()); - - assertEquals("value", result.get("string")); - assertEquals("4", result.get("integer")); - assertEquals("true", result.get("boolean")); - assertEquals("{}", result.get("emptyObject")); - assertEquals("[]", result.get("emptyList")); - assertEquals("{innerKey=innerValue}", result.get("object")); - assertEquals("[1, 2, 3]", result.get("list")); - } - - /** - * Tests the {@link Helpers#loadStringMappingFromJsonFile(Context, int)} method for the case - * if a non-existent file is provided. - */ - @Test - public void testLoadStringMappingFromJsonFileNegative() { - - Map result = Helpers.loadStringMappingFromJsonFile(InstrumentationRegistry.getContext(), - R.string.non_existent_file); - assertNotNull(result); - assertEquals("Expecting an empty map if using a non-existent file", 0, result.size()); - } -} \ No newline at end of file diff --git a/Utils/src/androidTest/java/com/amazon/android/utils/MapHelperTest.java b/Utils/src/androidTest/java/com/amazon/android/utils/MapHelperTest.java new file mode 100644 index 0000000..698b076 --- /dev/null +++ b/Utils/src/androidTest/java/com/amazon/android/utils/MapHelperTest.java @@ -0,0 +1,196 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.utils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; + +/** + * Tests the {@link MapHelper} class. + */ +@SuppressWarnings("unchecked") +@RunWith(AndroidJUnit4.class) + +public class MapHelperTest { + + private static final String TAG = MapHelperTest.class.getSimpleName(); + + /** + * Tests the {@link MapHelper#loadStringMappingFromJsonFile(Context, int)} method for the + * positive case. + */ + @Test + public void testLoadStringMappingFromJsonFilePositive() { + + HashMap result = + MapHelper.loadStringMappingFromJsonFile(InstrumentationRegistry.getContext(), + com.amazon.utils.test.R.string + .load_string_mapping_test1_file); + + assertEquals("There should be 7 key-value pairs in the map", 7, result.size()); + + assertEquals("value", result.get("string")); + assertEquals("4", result.get("integer")); + assertEquals("true", result.get("boolean")); + assertEquals("{}", result.get("emptyObject")); + assertEquals("[]", result.get("emptyList")); + assertEquals("{innerKey=innerValue}", result.get("object")); + assertEquals("[1, 2, 3]", result.get("list")); + } + + /** + * Tests the {@link MapHelper#loadStringMappingFromJsonFile(Context, int)} method for the case + * if a non-existent file is provided. + */ + @Test + public void testLoadStringMappingFromJsonFileNegative() { + + Map result = MapHelper.loadStringMappingFromJsonFile(InstrumentationRegistry.getContext(), + com.amazon.utils.test.R.string + .non_existent_file); + assertNotNull(result); + assertEquals("Expecting an empty map if using a non-existent file", 0, result.size()); + } + + /** + * create requested HashMap + * + * @param key of map + * @param value of map + * @return return map with given key and value + */ + Map getHashMap(String key, String value) { + + Map map = new HashMap<>(); + map.put(key, value); + return map; + } + + /** + * Inner class to provide generic implementation of HashSet + */ + private class TestHashSet { + + private HashSet testSet = new HashSet<>(); + + public void add(T... values) { + + testSet.addAll(Arrays.asList(values)); + } + + public HashSet get() { + + return testSet; + } + } + + /** + * create requested test HashSet + * + * @param type Type of object + * @return return HashSet for test + */ + private TestHashSet getTestHashSetObject(String type) { + + switch (type) { + case "strings": + TestHashSet t1 = new TestHashSet<>(); + t1.add("value1", "value2"); + return t1; + case "integers": + TestHashSet t2 = new TestHashSet<>(); + t2.add(4, 7, 13); + return t2; + case "booleans": + TestHashSet t3 = new TestHashSet<>(); + t3.add(true, false); + return t3; + case "emptyObjects": + TestHashSet t4 = new TestHashSet<>(); + t4.add(new HashMap()); + return t4; + case "emptyLists": + TestHashSet t5 = new TestHashSet<>(); + t5.add(new ArrayList()); + return t5; + case "objects": + TestHashSet> t6 = new TestHashSet<>(); + t6.add(getHashMap("innerKey1", "innerValue1"), + getHashMap("innerKey2", "innerValue2"), + getHashMap("innerKey3", "innerValue3")); + return t6; + case "lists": + TestHashSet> t7 = new TestHashSet<>(); + List l1 = Arrays.asList(1, 2, 3); + List l2 = Arrays.asList(4, 5, 6); + List l3 = Arrays.asList(13, 16, 20); + t7.add(l1, l2, l3); + return t7; + } + return new TestHashSet<>(); + } + + /** + * Tests the {@link MapHelper#loadArrayMappingFromJsonFile(Context, int)} method for the + * positive case. + */ + @Test + public void testLoadArrayMappingFromJsonFilePositive() { + + HashMap> result = + MapHelper.loadArrayMappingFromJsonFile(InstrumentationRegistry.getContext(), + com.amazon.utils.test.R.string + .load_array_mapping_test1_file); + + assertEquals("There should be 7 key-value pairs in the map", 7, result.size()); + + assertTrue(getTestHashSetObject("strings").get().equals(result.get("strings"))); + assertTrue(getTestHashSetObject("integers").get().equals(result.get("integers"))); + assertTrue(getTestHashSetObject("booleans").get().equals(result.get("booleans"))); + assertTrue(getTestHashSetObject("emptyObjects").get().equals(result.get("emptyObjects"))); + assertTrue(getTestHashSetObject("emptyLists").get().equals(result.get("emptyLists"))); + assertTrue(getTestHashSetObject("objects").get().equals(result.get("objects"))); + assertTrue(getTestHashSetObject("lists").get().equals(result.get("lists"))); + } + + /** + * Tests the {@link MapHelper#loadArrayMappingFromJsonFile(Context, int)} method for the case + * if a non-existent file is provided. + */ + @Test + public void testLoadArrayMappingFromJsonFileNegative() { + + Map result = MapHelper.loadArrayMappingFromJsonFile(InstrumentationRegistry.getContext(), + com.amazon.utils.test.R.string + .non_existent_file); + assertNotNull(result); + assertEquals("Expecting an empty map if using a non-existent file", 0, result.size()); + } +} \ No newline at end of file diff --git a/Utils/src/androidTest/java/com/amazon/android/utils/NetworkUtilsTest.java b/Utils/src/androidTest/java/com/amazon/android/utils/NetworkUtilsTest.java index bc6677d..093cf45 100644 --- a/Utils/src/androidTest/java/com/amazon/android/utils/NetworkUtilsTest.java +++ b/Utils/src/androidTest/java/com/amazon/android/utils/NetworkUtilsTest.java @@ -104,6 +104,8 @@ public void testAddParameterToUrl() throws Exception { ".php?app_id=257&app_key=0ojbgtfcsq12&action" + "=channels_videos&newparam=differentvalue"; + String url5 = "http://www.someurlwithoutaquery.com"; + String param = "newparam"; String value = "value1"; @@ -132,5 +134,8 @@ public void testAddParameterToUrl() throws Exception { // Test adding a new parameter to url that already contains the parameter. assertEquals("value1 should not have been added.", expected3, NetworkUtils.addParameterToUrl(url4, param, value)); + // Test adding a new parameter to a url that doesn't have a query. + assertEquals("value1 should not have been added.", + url5, NetworkUtils.addParameterToUrl(url5, param, value)); } } diff --git a/Utils/src/androidTest/java/com/amazon/utils/DateAndTimeHelperTest.java b/Utils/src/androidTest/java/com/amazon/utils/DateAndTimeHelperTest.java new file mode 100644 index 0000000..723dd98 --- /dev/null +++ b/Utils/src/androidTest/java/com/amazon/utils/DateAndTimeHelperTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.utils; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * Tests the {@link DateAndTimeHelper} class. + */ +public class DateAndTimeHelperTest { + + /** + * Tests the {@link DateAndTimeHelper#convertDateFormatToSeconds(String)} method. + */ + @Test + public void testConvertTimeOffsetFromDateFormat() throws Exception { + + assertTrue(60 == DateAndTimeHelper.convertDateFormatToSeconds("00:01:00.000")); + assertTrue(0 == DateAndTimeHelper.convertDateFormatToSeconds("00:00:00.000")); + assertTrue(10 == DateAndTimeHelper.convertDateFormatToSeconds("00:00:10.000")); + assertTrue(100 == DateAndTimeHelper.convertDateFormatToSeconds("00:01:40.000")); + assertTrue(0 == DateAndTimeHelper.convertDateFormatToSeconds("00:00:00.100")); + assertTrue(36000 == DateAndTimeHelper.convertDateFormatToSeconds("10:00:00.000")); + assertTrue(60 == DateAndTimeHelper.convertDateFormatToSeconds("00:01:00")); + assertTrue(-1 == DateAndTimeHelper.convertDateFormatToSeconds("00:00:0000")); + assertTrue(360000 == DateAndTimeHelper.convertDateFormatToSeconds("100:00:00")); + } +} diff --git a/Utils/src/androidTest/res/values/strings.xml b/Utils/src/androidTest/res/values/strings.xml index 9e062f9..49ad81d 100644 --- a/Utils/src/androidTest/res/values/strings.xml +++ b/Utils/src/androidTest/res/values/strings.xml @@ -14,5 +14,6 @@ permissions and limitations under the License. --> LoadStringMappingTest1.json + LoadArrayMappingTest1.json FakeFile \ No newline at end of file diff --git a/Utils/src/main/java/com/amazon/android/navigator/NavigatorModel.java b/Utils/src/main/java/com/amazon/android/navigator/NavigatorModel.java index 40f5ef9..4a05723 100644 --- a/Utils/src/main/java/com/amazon/android/navigator/NavigatorModel.java +++ b/Utils/src/main/java/com/amazon/android/navigator/NavigatorModel.java @@ -83,6 +83,11 @@ public class Config { */ public String searchAlgo; + /** + * Enable CEA-608 closed caption flag. If enabled, we prioritize CEA-608 captions. + */ + public boolean enableCEA608 = false; + /** * The number of global recommendations that the app should send; assuming there are * global recommendation recipes available. diff --git a/Utils/src/main/java/com/amazon/android/utils/Helpers.java b/Utils/src/main/java/com/amazon/android/utils/Helpers.java index b03999e..74ae69f 100644 --- a/Utils/src/main/java/com/amazon/android/utils/Helpers.java +++ b/Utils/src/main/java/com/amazon/android/utils/Helpers.java @@ -45,8 +45,6 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.Date; -import java.util.HashMap; -import java.util.Map; /** * A collection of utility methods, all static. @@ -302,42 +300,6 @@ public static Charset getDefaultAppCharset() { } } - /** - * Load a map of strings for the given JSON file. The file should be formatted as a flat - * object with string key, value pairs, e.g.: - * - * { - * "Key1", "Value1", - * "Key2", "Value2" - * } - * - * @param context Context. - * @param fileNameId File name ID of the file to read from. - * @return The JSON file parsed as a map of strings. If there was an error while reading the - * file such as the file not existing, an empty map is returned and the error is logged. - */ - public static HashMap loadStringMappingFromJsonFile(Context context, - int fileNameId) { - - HashMap result = new HashMap<>(); - String fileName = context.getString(fileNameId); - try { - if (FileHelper.doesFileExist(context, fileName)) { - String fileData = FileHelper.readFile(context, fileName); - Map map = JsonHelper.stringToMap(fileData); - - for (Object key : map.keySet()) { - result.put((String) key, String.valueOf(map.get(key))); - } - } - } - catch (Exception e) { - Log.w(TAG, "Unable to read file " + fileName, e); - } - - return result; - } - /** * Check that console output contains the specified text * diff --git a/Utils/src/main/java/com/amazon/android/utils/MapHelper.java b/Utils/src/main/java/com/amazon/android/utils/MapHelper.java new file mode 100644 index 0000000..2dfc098 --- /dev/null +++ b/Utils/src/main/java/com/amazon/android/utils/MapHelper.java @@ -0,0 +1,115 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.android.utils; + +import android.content.Context; +import android.util.Log; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +/** + * A collection of static utility methods to create maps from different sources like Json file. + */ +public class MapHelper { + + private static final String TAG = MapHelper.class.getSimpleName(); + public static final boolean DEBUG = false; + + /** + * Making sure public utility methods remain static. + */ + private MapHelper() { + + } + + /** + * Load a map of strings for the given JSON file. The file should be formatted as a flat + * object with string key, value pairs, e.g.: + * + * { + * "Key1", "Value1", + * "Key2", "Value2" + * } + * + * @param context Context. + * @param fileNameId File name ID of the file to read from. + * @return The JSON file parsed as a map of strings. If there was an error while reading the + * file such as the file not existing, an empty map is returned and the error is logged. + */ + public static HashMap loadStringMappingFromJsonFile(Context context, + int fileNameId) { + + HashMap result = new HashMap<>(); + String fileName = context.getString(fileNameId); + try { + if (FileHelper.doesFileExist(context, fileName)) { + String fileData = FileHelper.readFile(context, fileName); + Map map = JsonHelper.stringToMap(fileData); + + for (Object key : map.keySet()) { + result.put((String) key, String.valueOf(map.get(key))); + } + } + } + catch (Exception e) { + Log.w(TAG, "Unable to read file " + fileName, e); + } + + return result; + } + + /** + * Load a map of Arrays for the given JSON file. The file should be formatted as a flat + * object with string key, array values, e.g.: + * + * { + * "Key1", ["Value1", "Value2", ..] + * "Key2", ["Value3", "Value4", ..] + * } + * + * @param context Context. + * @param fileNameId File name ID of the file to read from. + * @return The JSON file parsed as a map of Hash set. If there was an error while reading the + * file such as the file not existing, an empty map is returned and the error is logged. + */ + public static HashMap> loadArrayMappingFromJsonFile(Context context, + int fileNameId) { + + HashMap> result = new HashMap<>(); + String fileName = context.getString(fileNameId); + try { + if (FileHelper.doesFileExist(context, fileName)) { + String fileData = FileHelper.readFile(context, fileName); + Map map = JsonHelper.stringToMap(fileData); + + for (Object key : map.keySet()) { + Object arrayObject = map.get(key); + if (arrayObject instanceof List) { + result.put((String) key, new HashSet((List) arrayObject)); + } + } + } + } + catch (Exception e) { + Log.w(TAG, "Unable to read file " + fileName, e); + } + + return result; + } +} diff --git a/Utils/src/main/java/com/amazon/android/utils/NetworkUtils.java b/Utils/src/main/java/com/amazon/android/utils/NetworkUtils.java index 614754e..05702cb 100644 --- a/Utils/src/main/java/com/amazon/android/utils/NetworkUtils.java +++ b/Utils/src/main/java/com/amazon/android/utils/NetworkUtils.java @@ -177,7 +177,7 @@ public static boolean urlContainsParameter(String urlString, String parameter) { /** * Adds a parameter with the given value to the query of a URL. It will not add the parameter - * if a value already exists for the parameter in the query. + * if a value already exists for the parameter in the query or if the URL has no query. * * @param urlString The URL. * @param parameter The parameter to add. @@ -197,22 +197,25 @@ public static String addParameterToUrl(String urlString, String parameter, Strin return newUrlString; } - // If the url doesn't have the parameter then just add it to the end. - if (queryParams.get(parameter) == null) { - newUrlString = urlString + "&" + parameter + "=" + value; - } - // If the url contains the parameter but with no value, insert the value. - else if (queryParams.get(parameter).isEmpty()) { - String[] split = urlString.split(parameter + "="); - if (split.length > 0) { - newUrlString = split[0] + parameter + "=" + value; - if (split.length > 1) { - newUrlString += split[1]; + // Only add the parameter if the original URL contains a query. + if (!queryParams.isEmpty()) { + // If the url doesn't have the parameter then just add it to the end. + if (queryParams.get(parameter) == null) { + newUrlString = urlString + "&" + parameter + "=" + value; + } + // If the url contains the parameter but with no value, insert the value. + else if (queryParams.get(parameter).isEmpty()) { + String[] split = urlString.split(parameter + "="); + if (split.length > 0) { + newUrlString = split[0] + parameter + "=" + value; + if (split.length > 1) { + newUrlString += split[1]; + } } } - } - else { - Log.d(TAG, "Cannot add parameter to URL because it already exists"); + else { + Log.d(TAG, "Cannot add parameter to URL because it already exists"); + } } return newUrlString; } @@ -229,21 +232,23 @@ private static Map getUrlQueryParameters(String urlString) throw Map queryParams = new HashMap<>(); URL url = new URL(urlString); String query = url.getQuery(); - String[] strParams = query.split("&"); - - for (String param : strParams) { - String[] split = param.split("="); - // Get the parameter name. - if (split.length > 0) { - String name = split[0]; - // Get the parameter value. - if (split.length > 1) { - String value = split[1]; - queryParams.put(name, value); - } - // If there is no value just put an empty string as placeholder. - else { - queryParams.put(name, ""); + if (query != null) { + String[] strParams = query.split("&"); + + for (String param : strParams) { + String[] split = param.split("="); + // Get the parameter name. + if (split.length > 0) { + String name = split[0]; + // Get the parameter value. + if (split.length > 1) { + String value = split[1]; + queryParams.put(name, value); + } + // If there is no value just put an empty string as placeholder. + else { + queryParams.put(name, ""); + } } } } diff --git a/Utils/src/main/java/com/amazon/utils/DateAndTimeHelper.java b/Utils/src/main/java/com/amazon/utils/DateAndTimeHelper.java index bb16acb..7eba1a3 100644 --- a/Utils/src/main/java/com/amazon/utils/DateAndTimeHelper.java +++ b/Utils/src/main/java/com/amazon/utils/DateAndTimeHelper.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * A copy of the License is located at * - * http://aws.amazon.com/apache2.0/ + * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either @@ -14,6 +14,10 @@ */ package com.amazon.utils; +import android.util.Log; + +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -22,6 +26,8 @@ */ public class DateAndTimeHelper { + public static final String TAG = DateAndTimeHelper.class.getSimpleName(); + /** * Returns current date. * @@ -58,4 +64,44 @@ public static boolean compareDates(Date oldDate, Date newDate) { return oldDate == null || oldDate.before(newDate); } + + /** + * Convert a simple date time with the format "HH:mm:ss.SSS" or "HH:mm:ss" to seconds. + * + * @param timeToConvert Time to convert. Expecting format of "HH:mm:ss.SSS" or "HH:mm:ss". + * @return The converted time in seconds. + */ + public static double convertDateFormatToSeconds(String timeToConvert) { + + if (timeToConvert == null) { + return -1; + } + + try { + SimpleDateFormat sdf; + + if (timeToConvert.matches("\\d+:\\d{2}:\\d{2}.\\d{3}")) { + sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + } + else if (timeToConvert.matches("\\d+:\\d{2}:\\d{2}")) { + sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + } + else { + throw new ParseException("Time format does not match expected.", 0); + } + + double time1 = sdf.parse("1970-01-01 00:00:00.000").getTime() / 1000; + + // if not valid, it will throw ParseException + Date date = sdf.parse("1970-01-01 " + timeToConvert); + return (date.getTime() / 1000) - time1; + + } + catch (ParseException e) { + Log.e(TAG, "Date to convert is not of a valid date format. Expecting \"HH:mm:ss.SSS\"" + + "or \"HH:mm:ss\""); + + return -1; + } + } } diff --git a/Utils/src/main/java/com/amazon/utils/ListUtils.java b/Utils/src/main/java/com/amazon/utils/ListUtils.java index 05302f9..aa8a9d9 100644 --- a/Utils/src/main/java/com/amazon/utils/ListUtils.java +++ b/Utils/src/main/java/com/amazon/utils/ListUtils.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; /** * Utilities for Lists. @@ -87,6 +88,26 @@ public static List stringToList(String string) throws ExpectingJsonArray return list; } + /** + * Get a map value from a map using a key and return it as a list. + * + * @param map The map to get the map value from. + * @param key The key to get the map value. + * @return A list of maps. + */ + public static List getValueAsMapList(Map map, String key) { + + if (map == null || StringManipulation.isNullOrEmpty(key) || map.get(key) == null) { + return new ArrayList<>(); + } + if (map.get(key) instanceof List) { + return (List) map.get(key); + } + List list = new ArrayList<>(); + list.add(map.get(key)); + return list; + } + /** * An error message for when a content's member variable is expected to be a string * representation of a JSON array but is something else. diff --git a/Utils/src/test/java/com/amazon/utils/ListUtilsTest.java b/Utils/src/test/java/com/amazon/utils/ListUtilsTest.java new file mode 100644 index 0000000..2b41741 --- /dev/null +++ b/Utils/src/test/java/com/amazon/utils/ListUtilsTest.java @@ -0,0 +1,70 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.utils; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * Tests the {@link ListUtils} class. + */ +public class ListUtilsTest { + + /** + * Tests the {@link ListUtils#getValueAsMapList(Map, String)} method. + */ + @Test + public void testGetValueAsMapList() { + + Map map = new HashMap<>(); + Map innerMap1 = new HashMap(); + innerMap1.put("key1", "value1"); + + Map innerMap2 = new HashMap(); + innerMap2.put("key1", "value2"); + + List list = new ArrayList<>(); + list.add(innerMap1); + list.add(innerMap2); + + map.put("mapKey", innerMap1); + map.put("listKey", list); + + List> expected = ListUtils.getValueAsMapList(map, "mapKey"); + assertEquals(1, expected.size()); + + expected = ListUtils.getValueAsMapList(map, "listKey"); + assertEquals(2, expected.size()); + + expected = ListUtils.getValueAsMapList(map, "badKey"); + assertEquals(0, expected.size()); + + expected = ListUtils.getValueAsMapList((Map) null, "key"); + assertEquals(0, expected.size()); + + expected = ListUtils.getValueAsMapList(map, null); + assertEquals(0, expected.size()); + + expected = ListUtils.getValueAsMapList(map, ""); + assertEquals(0, expected.size()); + } + +} diff --git a/VastAdsComponent/build.gradle b/VastAdsComponent/build.gradle index 31bc8e6..cf106b6 100644 --- a/VastAdsComponent/build.gradle +++ b/VastAdsComponent/build.gradle @@ -23,6 +23,8 @@ android { targetSdkVersion 23 versionCode 1 versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } buildTypes { release { @@ -38,10 +40,18 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile files('libs/mfXerces.jar') - testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' + androidTestCompile('com.android.support.test:runner:0.4') { + exclude group: 'com.android.support', module: 'support-annotations' + } + androidTestCompile('com.android.support.test:rules:0.4') { + exclude group: 'com.android.support', module: 'support-annotations' + } + androidTestCompile 'junit:junit:4.12' + compile project(':ModuleInterface') compile project(':AdsInterface') + compile project(':DynamicParser') compile project(':Utils') } diff --git a/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/model/vmap/AdBreakTest.java b/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/model/vmap/AdBreakTest.java new file mode 100644 index 0000000..5c5b5cd --- /dev/null +++ b/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/model/vmap/AdBreakTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests the {@link AdBreak} class. + */ +public class AdBreakTest { + + private AdBreak mAdBreak; + + @Before + public void setUp() { + + mAdBreak = new AdBreak(); + mAdBreak.setTimeOffset("end"); + } + + @Test + public void getTimeOffset() throws Exception { + + assertEquals("end", mAdBreak.getTimeOffset()); + } + + @Test + public void setTimeOffset() throws Exception { + + mAdBreak.setTimeOffset("start"); + assertEquals("start", mAdBreak.getTimeOffset()); + } + + @Test + public void getConvertedTimeOffset() throws Exception { + + assertTrue(-1 == mAdBreak.getConvertedTimeOffset()); + mAdBreak.setTimeOffset("00:00:15.000"); + assertTrue(15 == mAdBreak.getConvertedTimeOffset()); + } +} diff --git a/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/processor/AdTagProcessorTest.java b/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/processor/AdTagProcessorTest.java new file mode 100644 index 0000000..612c5e5 --- /dev/null +++ b/VastAdsComponent/src/androidTest/java/com/amazon/android/ads/vast/processor/AdTagProcessorTest.java @@ -0,0 +1,745 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.processor; + +import com.amazon.android.ads.vast.model.vast.Companion; +import com.amazon.android.ads.vast.model.vast.CompanionAd; +import com.amazon.android.ads.vast.model.vast.Creative; +import com.amazon.android.ads.vast.model.vast.Inline; +import com.amazon.android.ads.vast.model.vast.LinearAd; +import com.amazon.android.ads.vast.model.vast.MediaFile; +import com.amazon.android.ads.vast.model.vast.VastResponse; +import com.amazon.android.ads.vast.model.vast.VastAd; +import com.amazon.android.ads.vast.model.vmap.AdBreak; +import com.amazon.android.ads.vast.model.vmap.AdSource; +import com.amazon.android.ads.vast.model.vmap.AdTagURI; +import com.amazon.android.ads.vast.model.vmap.Extension; +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapResponse; +import com.amazon.android.ads.vast.test.R; +import com.amazon.android.ads.vast.util.DefaultMediaPicker; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Tests the {@link AdTagProcessor} class. Makes sure it can process the VMAP tag urls from + * https://developers.google.com/interactive-media-ads/docs/sdks/html5/tags. + */ +@RunWith(AndroidJUnit4.class) +public class AdTagProcessorTest { + + private AdTagProcessor mAdTagProcessor; + + @Before + public void setUp() { + + mAdTagProcessor = new AdTagProcessor(new DefaultMediaPicker(InstrumentationRegistry + .getContext())); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with an invalid tag url. The result + * should be an error value. + */ + @Test + public void testProcessError() throws Exception { + + assertTrue(mAdTagProcessor.process("some bad text") == AdTagProcessor.AdTagType.error); + + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VAST 2.0 tag. + */ + @Test + public void testProcessVast2Ad() throws Exception { + + String vast2Ad = InstrumentationRegistry.getContext().getString(R.string.vast_2_tag); + assertTrue(mAdTagProcessor.process(vast2Ad) == AdTagProcessor.AdTagType.vast); + + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VAST 2.0 wrapper tag. + */ + @Test + public void testProcessVast2WrapperAd() throws Exception { + + String vast2Ad = + InstrumentationRegistry.getContext().getString(R.string.vast_2_wrapper_tag); + + assertTrue(mAdTagProcessor.process(vast2Ad) == AdTagProcessor.AdTagType.vast); + + VastResponse vastResponse = mAdTagProcessor.getAdResponse().getAdBreaks().get(0) + .getAdSource().getVastResponse(); + + assertNotNull(vastResponse); + assertEquals("2.0", vastResponse.getVersion()); + + Inline inline = vastResponse.getInlineAds().get(0); + assertNotNull(inline); + + + assertEquals(2, inline.getImpressions().size()); + assertEquals("http://www.target.com/impression?impression", + inline.getImpressions().get(0)); + assertEquals("http://www.wrapper1.com/impression?impression", + inline.getImpressions().get(1)); + + assertEquals(1, inline.getErrorUrls().size()); + assertEquals("http://www.wrapper1.com/error", inline.getErrorUrls().get(0)); + + assertEquals(1, inline.getCreatives().size()); + Creative creative = inline.getCreatives().get(0); + assertEquals("00:00:15", ((LinearAd) creative.getVastAd()).getDuration()); + assertEquals(9, creative.getVastAd().getTrackingEvents().size()); + assertEquals(1, creative.getVastAd().getMediaFiles().size()); + + HashMap> trackingEvents = inline.getTrackingEvents(); + assertEquals(2, trackingEvents.get(Tracking.FIRST_QUARTILE_TYPE).size()); + assertTrue(trackingEvents.get(Tracking.FIRST_QUARTILE_TYPE) + .contains("http://www.target.com/event?firstQuartile")); + assertTrue(trackingEvents.get(Tracking.FIRST_QUARTILE_TYPE) + .contains("http://www.wrapper1.com/event?firstQuartile")); + assertEquals(2, trackingEvents.get(Tracking.MIDPOINT_TYPE).size()); + assertEquals(2, trackingEvents.get(Tracking.THIRD_QUARTILE_TYPE).size()); + assertEquals(2, trackingEvents.get(Tracking.COMPLETE_TYPE).size()); + assertEquals(1, trackingEvents.get(Tracking.MUTE_TYPE).size()); + + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VAST 3.0 wrapper tag. + */ + @Test + public void testProcessVast3WrapperAd() throws Exception { + + String vast3Ad = + InstrumentationRegistry.getContext().getString(R.string.vast_3_wrapper_tag); + + assertTrue(mAdTagProcessor.process(vast3Ad) == AdTagProcessor.AdTagType.vast); + + VastResponse vastResponse = mAdTagProcessor.getAdResponse().getAdBreaks().get(0) + .getAdSource().getVastResponse(); + + assertNotNull(vastResponse); + assertEquals("3.0", vastResponse.getVersion()); + Inline inline = vastResponse.getInlineAds().get(0); + assertNotNull(inline); + + + assertEquals(2, inline.getImpressions().size()); + + assertEquals(2, inline.getErrorUrls().size()); + + assertEquals(2, inline.getCreatives().size()); + Creative creative = inline.getCreatives().get(0); + assertEquals("00:00:10", ((LinearAd) creative.getVastAd()).getDuration()); + assertEquals(32, creative.getVastAd().getTrackingEvents().size()); + assertEquals(11, creative.getVastAd().getMediaFiles().size()); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VAST 3.0 tag. + */ + @Test + public void testProcessVastPreRoll() throws Exception { + + String vastPreRollTag = + InstrumentationRegistry.getContext().getString(R.string.vast_preroll_tag); + + assertTrue(mAdTagProcessor.process(vastPreRollTag) == AdTagProcessor.AdTagType.vast); + + VastResponse vastResponse = mAdTagProcessor.getAdResponse().getAdBreaks().get(0) + .getAdSource().getVastResponse(); + + assertNotNull(vastResponse); + + assertEquals("3.0", vastResponse.getVersion()); + Inline inline = vastResponse.getInlineAds().get(0); + assertNotNull(inline); + List creativeList = inline.getCreatives(); + assertEquals(2, creativeList.size()); + + Creative creative = creativeList.get(0); + assertEquals("57859154776", creative.getId()); + assertEquals("1", creative.getSequence()); + + VastAd vastAd = creative.getVastAd(); + assertNotNull(vastAd); + assertTrue(vastAd instanceof LinearAd); + LinearAd linearAd = (LinearAd) vastAd; + + List mediaFiles = linearAd.getMediaFiles(); + assertEquals(11, mediaFiles.size()); + + creative = creativeList.get(1); + assertEquals("57857370976", creative.getId()); + assertEquals("1", creative.getSequence()); + + vastAd = creative.getVastAd(); + assertNotNull(vastAd); + assertTrue(vastAd instanceof CompanionAd); + List companionList = ((CompanionAd) vastAd).getCompanions(); + assertEquals(1, companionList.size()); + Companion companion = companionList.get(0); + assertEquals("250", companion.getHeight()); + assertEquals("300", companion.getWidth()); + assertEquals("57857370976", companion.getId()); + assertEquals(1, companion.getTrackings().size()); + assertEquals("creativeView", companion.getTrackings().get(0).getEvent()); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a bad ad tag url. + */ + @Test + public void testProcessAdTagFailure() throws Exception { + + assertEquals(AdTagProcessor.AdTagType.error, mAdTagProcessor.process("bad url")); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP pre-roll tag. + */ + @Test + public void testProcessVmapPreRoll() throws Exception { + + + String vmapPreRollTag = + InstrumentationRegistry.getContext().getString(R.string.vmap_preroll_tag); + + assertEquals(AdTagProcessor.AdTagType.vmap, mAdTagProcessor.process(vmapPreRollTag)); + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + assertNotNull(vmapResponse); + + assertEquals(1, vmapResponse.getAdBreaks().size()); + + AdBreak adBreak = vmapResponse.getAdBreaks().get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP pre-roll tag with + * ad bumpers. + */ + @Test + public void testProcessVmapPreRollBumper() throws Exception { + + String vmapPreRollBumperTag = + InstrumentationRegistry.getContext().getString(R.string.vmap_preroll_bumper_tag); + + assertEquals(AdTagProcessor.AdTagType.vmap, mAdTagProcessor.process(vmapPreRollBumperTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + assertNotNull(vmapResponse); + + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(2, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-post-bumper", false, true); + + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + assertEquals(1, adBreak.getExtensions().size()); + Extension extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP post-roll tag. + */ + @Test + public void testVmapPostRoll() throws Exception { + + String vmapPostRollTag = + InstrumentationRegistry.getContext().getString(R.string.vmap_postroll_tag); + + assertEquals(AdTagProcessor.AdTagType.vmap, mAdTagProcessor.process(vmapPostRollTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + assertNotNull(vmapResponse); + + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(1, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP post-roll tag with ad + * bumpers. + */ + @Test + public void testVmapPostRollBumper() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_postroll_bumper_tag); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + assertNotNull(vmapResponse); + + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(2, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-pre-bumper", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + assertEquals(1, adBreak.getExtensions().size()); + Extension extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP tag that has pre-, mid-, + * and post-roll single ads. + */ + @Test + public void testVmapPreMidPostRollsSingleAds() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_pre_mid_post_rolls_single_ads_tag); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + assertNotNull(vmapResponse); + + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(3, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check third ad break. + adBreak = adBreakList.get(2); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP tag that has a single + * pre-roll ad, a mid-roll standard ad pod with 3 ads, and a single post-roll ad. + */ + @Test + public void testVmapStandardPodWith3Ads() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_standard_pod_with_3_ads); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(5, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check third ad break. + adBreak = adBreakList.get(2); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-2", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check fourth ad break. + adBreak = adBreakList.get(3); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-3", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check fifth ad break. + adBreak = adBreakList.get(4); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP tag that has a single + * pre-roll ad, a mid-roll optimized ad pod with 3 ads, and a single post-roll ad. + */ + @Test + public void testVmapOptimizedPodWith3Ads() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_optimized_pod_with_3_ads); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(3, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ads", true, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check third ad break. + adBreak = adBreakList.get(2); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP tag that has a single + * pre-roll ad, a mid-roll standard ad pod with 3 ads, and a single post-roll ad. All ad + * breaks have bumpers around them. + */ + @Test + public void testVmapStandardPodWith3AdsWithBumpers() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_standard_pod_with_3_ads_bumpers); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(9, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-post-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + Extension extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check third ad break. + adBreak = adBreakList.get(2); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-pre-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check fourth ad break. + adBreak = adBreakList.get(3); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check fifth ad break. + adBreak = adBreakList.get(4); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-2", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check sixth ad break. + adBreak = adBreakList.get(5); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ad-3", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check seventh ad break. + adBreak = adBreakList.get(6); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-post-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check eighth ad break. + adBreak = adBreakList.get(7); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-pre-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check ninth ad break. + adBreak = adBreakList.get(8); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Tests the {@link AdTagProcessor#process(String)} method with a VMAP tag that has a single + * pre-roll ad, a mid-roll optimzied ad pod with 3 ads, and a single post-roll ad. All ad + * breaks have bumpers around them. + */ + @Test + public void testVmapOptimizedPodWith3AdsWithBumpers() throws Exception { + + String vmapPreMidPostSingleAdTag = InstrumentationRegistry + .getContext().getString(R.string.vmap_optimized_pod_with_3_ads_bumpers); + + assertEquals(AdTagProcessor.AdTagType.vmap, + mAdTagProcessor.process(vmapPreMidPostSingleAdTag)); + + VmapResponse vmapResponse = mAdTagProcessor.getAdResponse(); + List adBreakList = vmapResponse.getAdBreaks(); + assertEquals(7, adBreakList.size()); + + // Check first ad break. + AdBreak adBreak = adBreakList.get(0); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + AdSource adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-ad-1", false, true); + VastResponse vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check second ad break. + adBreak = adBreakList.get(1); + checkAdBreakAttributes(adBreak, "start", "linear", "preroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "preroll-post-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + Extension extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check third ad break. + adBreak = adBreakList.get(2); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-pre-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check fourth ad break. + adBreak = adBreakList.get(3); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-ads", true, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + + // Check fifth ad break. + adBreak = adBreakList.get(4); + checkAdBreakAttributes(adBreak, "00:00:15.000", "linear", "midroll-1"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "midroll-1-post-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check sixth ad break. + adBreak = adBreakList.get(5); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-pre-bumper", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + extension = adBreak.getExtensions().get(0); + checkExtensionAttributes(extension, "bumper", true); + + // Check seventh ad break. + adBreak = adBreakList.get(6); + checkAdBreakAttributes(adBreak, "end", "linear", "postroll"); + adSource = adBreak.getAdSource(); + checkAdSourceAttributes(adSource, "postroll-ad-1", false, true); + vastResponse = adSource.getVastResponse(); + assertNotNull(vastResponse); + } + + /** + * Checks the ad break for the expected attribute values. + * + * @param adBreak The ad break. + * @param timeOffset The expected time offset. + * @param breakType The expected break type. + * @param breakId The expected break id. + */ + private void checkAdBreakAttributes(AdBreak adBreak, String timeOffset, String breakType, + String breakId) { + + assertNotNull(adBreak); + assertEquals(timeOffset, adBreak.getTimeOffset()); + assertEquals(breakType, adBreak.getBreakType()); + assertEquals(breakId, adBreak.getBreakId()); + } + + /** + * Checks the ad source for the expected attribute values. + * + * @param adSource Teh ad source. + * @param id The expected id. + * @param allowMultipleAds The expected value for allow multiple ads. + * @param followRedirects The expected value for follow redirects. + */ + private void checkAdSourceAttributes(AdSource adSource, String id, boolean allowMultipleAds, + boolean followRedirects) { + + assertNotNull(adSource); + assertEquals(id, adSource.getId()); + assertEquals(allowMultipleAds, adSource.isAllowMultipleAds()); + assertEquals(followRedirects, adSource.isFollowRedirects()); + } + + /** + * Checks the ad tag URI for the expected attribute values. Also checks that the URI is not an + * empty string. + * + * @param adTagURI The ad tag URI. + * @param templateType The expected template type. + */ + private void checkAdTagUriAttributes(AdTagURI adTagURI, String templateType) { + + assertNotNull(adTagURI); + assertEquals(templateType, adTagURI.getTemplateType()); + assertFalse(adTagURI.getUri().isEmpty()); + } + + /** + * Checks the extension for the expected attribute values. + * + * @param extension The extension. + * @param type The expected type. + * @param suppressBumper The expected value for suppress bumper. + */ + private void checkExtensionAttributes(Extension extension, String type, boolean + suppressBumper) { + + assertNotNull(extension); + assertEquals(type, extension.getType()); + assertEquals(suppressBumper, extension.isSuppressBumper()); + } +} \ No newline at end of file diff --git a/VastAdsComponent/src/androidTest/res/values/values.xml b/VastAdsComponent/src/androidTest/res/values/values.xml new file mode 100644 index 0000000..35185d5 --- /dev/null +++ b/VastAdsComponent/src/androidTest/res/values/values.xml @@ -0,0 +1,53 @@ + + + + https://raw.githubusercontent.com/nexage/sourcekit-vast-android/master/demo/app/assets/WrapperSimple.xml + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirectlinear&correlator= + + https://raw.githubusercontent.com/nexage/sourcekit-vast-android/master/demo/app/assets/Simple.xml + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator= + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator= + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonlybumper&cmsid=496&vid=short_onecue&correlator= + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonly&cmsid=496&vid=short_onecue&correlator= + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonlybumper&cmsid=496&vid=short_onecue&correlator= + + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator= + + + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator= + + + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpod&cmsid=496&vid=short_onecue&correlator= + + + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpodbumper&cmsid=496&vid=short_onecue&correlator= + + + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator= + + + \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/VASTAdsPlayer.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/VASTAdsPlayer.java index 63669c4..2ad16ef 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/VASTAdsPlayer.java +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/VASTAdsPlayer.java @@ -45,14 +45,21 @@ package com.amazon.android.ads.vast; import com.amazon.ads.IAds; -import com.amazon.android.ads.vast.model.TRACKING_EVENTS_TYPE; -import com.amazon.android.ads.vast.model.VASTModel; -import com.amazon.android.ads.vast.processor.VASTMediaPicker; -import com.amazon.android.ads.vast.processor.VASTProcessor; +import com.amazon.android.ads.vast.model.vast.Creative; +import com.amazon.android.ads.vast.model.vast.Inline; +import com.amazon.android.ads.vast.model.vast.LinearAd; +import com.amazon.android.ads.vast.model.vast.VastAd; +import com.amazon.android.ads.vast.model.vast.VastResponse; +import com.amazon.android.ads.vast.model.vmap.AdBreak; +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapResponse; +import com.amazon.android.ads.vast.processor.AdTagProcessor; +import com.amazon.android.ads.vast.processor.MediaPicker; +import com.amazon.android.ads.vast.processor.ResponseValidator; +import com.amazon.android.ads.vast.util.VastAdListener; import com.amazon.android.ads.vast.util.DefaultMediaPicker; import com.amazon.android.ads.vast.util.HttpTools; import com.amazon.android.ads.vast.util.NetworkTools; -import com.amazon.android.ads.vast.util.VASTLog; import com.amazon.android.utils.NetworkUtils; import android.app.Activity; @@ -60,23 +67,25 @@ import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; +import android.os.SystemClock; import android.util.DisplayMetrics; +import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.widget.FrameLayout; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Timer; import java.util.TimerTask; +import static com.amazon.utils.DateAndTimeHelper.convertDateFormatToSeconds; + /** - * This implementation of VAST is experimental and under development. Very limited support and - * documentation is provided. This can be used as a proof of concept for VAST but all code and + * This implementation of VAST is experimental and under development. Very limited support and + * documentation is provided. This can be used as a proof of concept for VAST but all code and * interfaces are subject to change. */ public class VASTAdsPlayer implements IAds, @@ -87,8 +96,8 @@ public class VASTAdsPlayer implements IAds, SurfaceHolder.Callback { private static final String TAG = VASTAdsPlayer.class.getSimpleName(); - public static final String VERSION = "1.3"; - private static final String CORRELATOR_PARAMETER = "correlator"; + public static final String VERSION = "2.0"; + // errors that can be returned in the vastError callback method of the // VASTPlayerListener public static final int ERROR_NONE = 0; @@ -100,15 +109,20 @@ public class VASTAdsPlayer implements IAds, public static final int ERROR_EXCEEDED_WRAPPER_LIMIT = 6; public static final int ERROR_VIDEO_PLAYBACK = 7; + /** + * constant to be used to convert seconds to milliseconds + */ + private static final int MILLISECONDS_IN_SECOND = 1000; + private static final long QUARTILE_TIMER_INTERVAL = 250; private Context mContext; private FrameLayout mFrameLayout; private Bundle mExtras; private IAdsEvents mIAdsEvents; + private Bundle mAdDetail; - private VASTModel mVASTModel; - private HashMap> mTrackingEventMap; + private HashMap> mTrackingEventMap; private MediaPlayer mMediaPlayer; private SurfaceView mSurfaceView; @@ -126,10 +140,21 @@ public class VASTAdsPlayer implements IAds, private Timer mTrackingEventTimer; private int mQuartile = 0; - private double mCurrentVideoPosition; - private PlayerState mPlayerState; private ActivityState mActivityState; + private VmapResponse mAdResponse; + private AdTagProcessor.AdTagType mAdType; + private List mMidRollAds; + private List mPostRollAds; + private List mPlayedMidRollAds; + private int mAdPlayedCount; + private String mCurrentAdType; + + /** + * Ad start time. + */ + private long mAdSlotStartTime; + @Override public void init(Context context, FrameLayout frameLayout, Bundle extras) { @@ -137,18 +162,17 @@ public void init(Context context, FrameLayout frameLayout, Bundle extras) { mFrameLayout = frameLayout; mExtras = extras; - DisplayMetrics displayMetrics = mContext.getResources() - .getDisplayMetrics(); + DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); mScreenWidth = displayMetrics.widthPixels; mScreenHeight = displayMetrics.heightPixels; - VASTLog.d(TAG, "Init called, version:" + VERSION); + Log.d(TAG, "Init called, version:" + VERSION); } @Override public void showPreRollAd() { - + Log.d(TAG, "showPreRollAd called"); cleanUpMediaPlayer(); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( @@ -156,16 +180,10 @@ public void showPreRollAd() { FrameLayout.LayoutParams.MATCH_PARENT); createSurface(params); + mCurrentAdType = IAds.PRE_ROLL_AD; + mAdPlayedCount = 0; - // Get the preroll url and give it a unique timestamp. - String preRollUrl = mContext.getResources().getString(R.string.vast_preroll_tag); - - // Try to add a correlator value. - preRollUrl = NetworkUtils.addParameterToUrl(preRollUrl, CORRELATOR_PARAMETER, - "" + System.currentTimeMillis()); - - - loadVideoWithUrl(preRollUrl); + loadAdFromUrl(); } @Override @@ -177,11 +195,49 @@ public void setIAdsEvents(IAdsEvents iAdsEvents) { @Override public void setCurrentVideoPosition(double position) { - mCurrentVideoPosition = position; + if (mMidRollAds != null && mMidRollAds.size() > 0 && + mPlayedMidRollAds.contains(false)) { + + int i = 0; + List midRollsToPlayNow = new ArrayList<>(); + for (AdBreak adBreak : mMidRollAds) { + + // Queue the add if the time offset matches the playback position + // and the ad hasn't been played yet. + if (adBreak.getConvertedTimeOffset() == ((int) position / 1000) + && !mPlayedMidRollAds.get(i)) { + + Log.d(TAG, "Mid roll " + i + " played=" + mPlayedMidRollAds.get(i) + + " compare :" + adBreak.getConvertedTimeOffset() + " == " + + (int) (position / 1000)); + + midRollsToPlayNow.add(adBreak); + mPlayedMidRollAds.set(i, true); + } + i++; + } + // Play any mid-roll ads that matched the playback position + if (!midRollsToPlayNow.isEmpty()) { + createMediaPlayer(); + + Log.d(TAG, "Play mid-rolls!!"); + mCurrentAdType = IAds.MID_ROLL_AD; + mAdListener.startAdPod(0, midRollsToPlayNow); + } + } + } + + @Override + public boolean isPostRollAvailable() { + + return mPostRollAds != null && mPostRollAds.size() > 0; } @Override public void setActivityState(ActivityState activityState) { + + Log.d(TAG, "Activity state changed from " + mActivityState + " to " + activityState); + mActivityState = activityState; activityStateChanged(activityState); } @@ -205,7 +261,15 @@ private void activityStateChanged(ActivityState activityState) { @Override public void setPlayerState(PlayerState playerState) { - mPlayerState = playerState; + if (playerState == PlayerState.COMPLETED) { + if (mPostRollAds != null && mPostRollAds.size() > 0) { + if (mAdListener != null) { + Log.d(TAG, "Start post roll ads"); + mCurrentAdType = IAds.POST_ROLL_AD; + mAdListener.startAdPod(0, mPostRollAds); + } + } + } } @Override @@ -223,100 +287,259 @@ public Bundle getExtra() { return mExtras; } - private void loadVideoWithUrl(final String urlString) { + /** + * return inline ads list + * + * @return List of inline ads. + */ + private List getInlineAds(AdBreak currentAd){ - VASTLog.d(TAG, "loadVideoWithUrl " + urlString); - mVASTModel = null; - if (NetworkTools.connectedToInternet(mContext)) { - (new Thread(new Runnable() { - @Override - public void run() { + List inlineAds = null; + VastResponse vastResponse = currentAd.getAdSource().getVastResponse(); + //need to check here for null as we can also get custom ad data in a vmap response + if (vastResponse != null) { + inlineAds = vastResponse.getInlineAds(); + } + return inlineAds; + } - BufferedReader in = null; - StringBuffer sb; - try { - URL url = new URL(urlString); + /** + * provide ad id from a inline ad + * + * @return String ad id. + */ + private String getCurrentAdId(AdBreak currentAd) { + + String adId = null; + //ToDo: we need to change this after we are having single inline in each ad + //instead of single ad with multiple inlines. + List inlineAds = getInlineAds(currentAd); + //We may possibly get a wrapper ad instead of a inline ad attribute + if (inlineAds != null && !inlineAds.isEmpty()) { + adId = inlineAds.get(0).getId(); + } + return adId; + } - in = new BufferedReader(new InputStreamReader(url.openStream())); - sb = new StringBuffer(); - String line; - while ((line = in.readLine()) != null) { - sb.append(line).append(System.getProperty("line.separator")); - } - } catch (Exception e) { - mVASTPlayerListener.vastError(ERROR_XML_OPEN_OR_READ); - VASTLog.e(TAG, e.getMessage(), e); - return; - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - // ignore - } - } - loadVideoWithData(sb.toString()); + /** + * provide duration from a linear ad + * + * @return String ad duration. + */ + private String getCurrentAdDuration(AdBreak currentAd) { + + String duration = null; + List inlineAds = getInlineAds(currentAd); + //We may possibly get a wrapper ad instead of a inline ad attribute + if (inlineAds != null && !inlineAds.isEmpty()) { + //ToDo: we may need to change this if we start selecting the best format + // in muliple creatives + List mCreatives = inlineAds.get(0).getCreatives(); + //creatives is a required field in linear ad + VastAd mVastAd = mCreatives.get(0).getVastAd(); + if (mVastAd instanceof LinearAd) { + duration = ((LinearAd) mVastAd).getDuration(); + } + } + return duration; + } + + /** + * provide basic ad details + * + * @return bundle containing ad details. + */ + private Bundle getBasicAdDetailBundle() { + + if (mAdDetail == null) { + mAdDetail = new Bundle(); + AdBreak currentAd = mAdResponse.getCurrentAd(); + if (currentAd != null) { + mAdDetail.putString(ID, getCurrentAdId(currentAd)); + mAdDetail.putString(AD_TYPE, mCurrentAdType); + String duration = getCurrentAdDuration(currentAd); + if (duration != null) { + mAdDetail.putLong(DURATION_RECEIVED, (long) (convertDateFormatToSeconds + (duration) * MILLISECONDS_IN_SECOND)); } - })).start(); - } else { - mVASTPlayerListener.vastError(ERROR_NO_NETWORK); + } } + return mAdDetail; } - private void loadVideoWithData(final String xmlData) { + /** + * Load data from the ad tag string resource and process the data to get the ad response. + */ + private void loadAdFromUrl() { + + Log.d(TAG, "Loading the ad model from url."); + mAdResponse = null; - VASTLog.v(TAG, "loadVideoWithData\n" + xmlData); - mVASTModel = null; if (NetworkTools.connectedToInternet(mContext)) { (new Thread(new Runnable() { @Override public void run() { - VASTMediaPicker mediaPicker = new DefaultMediaPicker(mContext); - VASTProcessor processor = new VASTProcessor(mediaPicker); - int error = processor.process(xmlData); - if (error == ERROR_NONE) { - mVASTModel = processor.getModel(); - mVASTPlayerListener.vastReady(); - } else { - mVASTPlayerListener.vastError(error); + MediaPicker mediaPicker = new DefaultMediaPicker(mContext); + AdTagProcessor adTagProcessor = new AdTagProcessor(mediaPicker); + + String adUrl = mContext.getResources().getString(R.string.ad_tag); + + // Try to add a correlator value to the url if needed. + adUrl = NetworkUtils.addParameterToUrl(adUrl, IAds.CORRELATOR_PARAMETER, + "" + System.currentTimeMillis()); + + mAdType = adTagProcessor.process(adUrl); + + if (mAdType != AdTagProcessor.AdTagType.error) { + mAdResponse = adTagProcessor.getAdResponse(); + mAdListener.adsReady(); + } + else { + mVASTPlayerListener.vastError(ERROR_XML_PARSE); } } })).start(); - } else { + } + else { mVASTPlayerListener.vastError(ERROR_NO_NETWORK); } } - // NOT BEING CALLED IN UI THREAD!!! + /** + * An ad listener that deals with playing ad pods (multiple ads at a time). + */ + private VastAdListener mAdListener = new VastAdListener() { + + List adList; + int adIdx; + + @Override + public void adsReady() { + + Log.d(TAG, "Ad models are ready"); + mMidRollAds = mAdResponse.getMidRollAdBreaks(); + mPostRollAds = mAdResponse.getPostRollAdBreaks(); + mPlayedMidRollAds = new ArrayList<>(); + for (int i = 0; i < mMidRollAds.size(); i++) { + mPlayedMidRollAds.add(false); + } + Log.d(TAG, "Starting pre-roll ads"); + startAdPod(0, mAdResponse.getPreRollAdBreaks()); + } + + @Override + public void startAdPod(int adIdx, List adList) { + + this.adIdx = adIdx; + this.adList = adList; + + // Capture ad start time. + mAdSlotStartTime = SystemClock.elapsedRealtime(); + startAd(); + } + + @Override + public void startAd() { + + Log.d(TAG, "start ad with index " + adIdx); + if (adIdx < adList.size()) { + if (mMediaPlayer == null) { + createMediaPlayer(); + } + mAdResponse.setCurrentAd(adList.get(adIdx)); + if (ResponseValidator.validateAdBreak(mAdResponse.getCurrentAd())) { + mVASTPlayerListener.vastReady(); + } + else { + Log.e(TAG, "Skipping invalid ad"); + adIdx++; + if (adIdx < adList.size()) { + startAd(); + } + else { + adPodComplete(); + } + } + } + } + + @Override + public void adComplete() { + + Log.d(TAG, "ad complete with index " + adIdx); + adIdx++; + mAdPlayedCount++; + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + } + cleanUpMediaPlayer(); + if (mIAdsEvents != null) { + // Calculate how long Ads played. + long adSlotTime = SystemClock.elapsedRealtime() - mAdSlotStartTime; + Bundle extras = getBasicAdDetailBundle(); + extras.putLong(DURATION_PLAYED, adSlotTime); + extras.putBoolean(IAds.AD_POD_COMPLETE, adIdx == adList.size()); + // Let listener know about Ad slot stop event. + mIAdsEvents.onAdSlotEnded(extras); + } + if (adIdx < adList.size()) { + startAd(); + } + else { + adPodComplete(); + } + } + + @Override + public void adPodComplete() { + Log.d(TAG, "ad pod complete"); + Log.d(TAG, "Played " + mAdPlayedCount + " of " + mAdResponse.getAdBreaks().size() + + " ads"); + + if (mAdPlayedCount == mAdResponse.getAdBreaks().size()) { + if (mVASTPlayerListener != null) { + mVASTPlayerListener.vastComplete(); + } + } + } + }; + + // NOT CALLED IN UI THREAD!!! private VASTPlayerListener mVASTPlayerListener = new VASTPlayerListener() { + @Override public void vastReady() { - mTrackingEventMap = mVASTModel.getTrackingUrls(); + Log.d(TAG, "Vast ready!"); - ((Activity) mContext).runOnUiThread(new Runnable() { - @Override - public void run() { + if (mAdResponse != null) { + + mTrackingEventMap = mAdResponse.getCurrentAd().getTrackingUrls(); - String url = mVASTModel.getPickedMediaFileURL(); + ((Activity) mContext).runOnUiThread(new Runnable() { + @Override + public void run() { - VASTLog.d(TAG, "URL for media file:" + url); - try { - mMediaPlayer.setDataSource(url); - } catch (IOException e) { - VASTLog.e(TAG, "Could not set data source for VAST ad",e); + String url = mAdResponse.getCurrentAd().getSelectedMediaFileUrl(); + + Log.d(TAG, "URL for media file:" + url); + try { + mMediaPlayer.setDataSource(url); + } + catch (IOException e) { + Log.e(TAG, "Could not set data source for VAST ad", e); + } + mMediaPlayer.prepareAsync(); } - mMediaPlayer.prepareAsync(); - } - }); + }); + } } @Override public void vastError(int error) { - VASTLog.e(TAG, "vastComplete:" + error); + Log.e(TAG, "vast error:" + error); ((Activity) mContext).runOnUiThread(new Runnable() { @Override @@ -328,7 +551,11 @@ public void run() { cleanUpMediaPlayer(); if (mIAdsEvents != null) { - mIAdsEvents.onAdSlotEnded(null); + // Calculate how long Ads played. + long adSlotTime = SystemClock.elapsedRealtime() - mAdSlotStartTime; + Bundle extras = getBasicAdDetailBundle(); + extras.putLong(DURATION_PLAYED, adSlotTime); + mIAdsEvents.onAdSlotEnded(extras); } } }); @@ -342,57 +569,53 @@ public void vastClick() { @Override public void vastComplete() { - VASTLog.e(TAG, "vastComplete"); - - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - } - cleanUpMediaPlayer(); - - if (mIAdsEvents != null) { - mIAdsEvents.onAdSlotEnded(null); - } + Log.d(TAG, "vastComplete"); + cleanUpSurface(); } @Override public void vastDismiss() { - VASTLog.d(TAG, "vastDismiss"); + + Log.d(TAG, "vastDismiss"); } }; @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { - VASTLog.d(TAG, "surfaceCreated -- (SurfaceHolder callback)"); + Log.d(TAG, "surfaceCreated -- (SurfaceHolder callback)"); try { if (mMediaPlayer == null) { createMediaPlayer(); } mMediaPlayer.setDisplay(mSurfaceHolder); - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); + } + catch (Exception e) { + Log.e(TAG, e.getMessage(), e); } } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { + } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { + } @Override public void onCompletion(MediaPlayer mediaPlayer) { - VASTLog.e(TAG, "entered onError -- (MediaPlayer callback)"); + Log.d(TAG, "entered onCompletion-- (MediaPlayer callback)"); if (!mIsPlayBackError && !mIsCompleted) { mIsCompleted = true; - this.processEvent(TRACKING_EVENTS_TYPE.complete); + this.processEvent(Tracking.COMPLETE_TYPE); - if (mVASTPlayerListener != null) { - mVASTPlayerListener.vastComplete(); + if (mAdListener != null) { + mAdListener.adComplete(); } } } @@ -400,9 +623,9 @@ public void onCompletion(MediaPlayer mediaPlayer) { @Override public boolean onError(MediaPlayer mediaPlayer, int what, int extra) { - VASTLog.e(TAG, "entered onError -- (MediaPlayer callback)"); + Log.e(TAG, "entered onError -- (MediaPlayer callback)"); mIsPlayBackError = true; - VASTLog.e(TAG, "Shutting down Activity due to Media Player errors: WHAT:" + what + ": " + + Log.e(TAG, "Shutting down Activity due to Media Player errors: WHAT:" + what + ": " + "EXTRA:" + extra + ":"); processErrorEvent(); @@ -415,7 +638,11 @@ public void onPrepared(MediaPlayer mediaPlayer) { calculateAspectRatio(); - mIAdsEvents.onAdSlotStarted(null); + mAdDetail = null; + mIAdsEvents.onAdSlotStarted(getBasicAdDetailBundle()); + + // Capture ad start time. + mAdSlotStartTime = SystemClock.elapsedRealtime(); mMediaPlayer.start(); @@ -439,27 +666,36 @@ public void onVideoSizeChanged(MediaPlayer mediaPlayer, int width, int height) { private void createSurface(FrameLayout.LayoutParams params) { + Log.d(TAG, "Creating surface"); mSurfaceView = new SurfaceView(mContext); mSurfaceView.setLayoutParams(params); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(this); - mFrameLayout.addView(mSurfaceView); } private void createMediaPlayer() { + Log.d(TAG, "create media player"); mMediaPlayer = new MediaPlayer(); mMediaPlayer.setOnCompletionListener(this); mMediaPlayer.setOnErrorListener(this); mMediaPlayer.setOnPreparedListener(this); mMediaPlayer.setOnVideoSizeChangedListener(this); mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + + ((Activity)mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + mFrameLayout.addView(mSurfaceView); + } + }); + } private void cleanUpMediaPlayer() { - VASTLog.d(TAG, "entered cleanUpMediaPlayer "); + Log.d(TAG, "entered cleanUpMediaPlayer "); if (mMediaPlayer != null) { @@ -475,23 +711,35 @@ private void cleanUpMediaPlayer() { mMediaPlayer.release(); mMediaPlayer = null; - mFrameLayout.removeView(mSurfaceView); - mSurfaceHolder.removeCallback(this); - mSurfaceHolder.getSurface().release(); - mSurfaceView = null; - mSurfaceHolder = null; + ((Activity)mContext).runOnUiThread(new Runnable() { + @Override + public void run() { + mFrameLayout.removeView(mSurfaceView); + } + }); mIsVideoPaused = false; mIsPlayBackError = false; mIsProcessedImpressions = false; mIsCompleted = false; + mQuartile = 0; } } - private void processEvent(TRACKING_EVENTS_TYPE eventName) { + /** + * Clean up the surface view. Should only be done after we're done using the player. + */ + private void cleanUpSurface() { + mSurfaceHolder.removeCallback(this); + mSurfaceHolder.getSurface().release(); + mSurfaceView = null; + mSurfaceHolder = null; + } + + private void processEvent(String eventName) { - VASTLog.i(TAG, "entered Processing Event: " + eventName); + Log.i(TAG, "entered Processing Event: " + eventName); List urls = mTrackingEventMap.get(eventName); fireUrls(urls); @@ -499,43 +747,43 @@ private void processEvent(TRACKING_EVENTS_TYPE eventName) { private void processErrorEvent() { - VASTLog.d(TAG, "entered processErrorEvent"); + Log.i(TAG, "entered processErrorEvent"); - List errorUrls = mVASTModel.getErrorUrl(); + List errorUrls = mAdResponse.getCurrentAd().getErrorUrls(); fireUrls(errorUrls); } private void processImpressions() { - VASTLog.d(TAG, "entered processImpressions"); + Log.i(TAG, "entered processImpressions"); mIsProcessedImpressions = true; - List impressions = mVASTModel.getImpressions(); + List impressions = mAdResponse.getCurrentAd().getImpressions(); fireUrls(impressions); } private void fireUrls(List urls) { - VASTLog.d(TAG, "entered fireUrls"); + Log.i(TAG, "entered fireUrls"); if (urls != null) { - for (String url : urls) { - VASTLog.v(TAG, "\tfiring url:" + url); + Log.i(TAG, "\tfiring url:" + url); HttpTools.httpGetURL(url); } - } else { - VASTLog.d(TAG, "\turl list is null"); + } + else { + Log.i(TAG, "\turl list is null"); } } private void startQuartileTimer() { - VASTLog.d(TAG, "entered startQuartileTimer"); + Log.i(TAG, "entered startQuartileTimer"); stopQuartileTimer(); if (mIsCompleted) { - VASTLog.d(TAG, "ending quartileTimer because the video has been replayed"); + Log.i(TAG, "ending quartileTimer because the video has been replayed"); return; } @@ -555,31 +803,29 @@ public void run() { return; } percentage = 100 * curPos / videoDuration; - } catch (Exception e) { - VASTLog.w(TAG, - "mediaPlayer.getCurrentPosition exception: " - + e.getMessage()); + } + catch (Exception e) { + Log.e(TAG, "mediaPlayer.getCurrentPosition exception: " + e.getMessage()); this.cancel(); return; } if (percentage >= 25 * mQuartile) { if (mQuartile == 0) { - VASTLog.i(TAG, "Video at start: (" + percentage - + "%)"); - processEvent(TRACKING_EVENTS_TYPE.start); - } else if (mQuartile == 1) { - VASTLog.i(TAG, "Video at first quartile: (" - + percentage + "%)"); - processEvent(TRACKING_EVENTS_TYPE.firstQuartile); - } else if (mQuartile == 2) { - VASTLog.i(TAG, "Video at midpoint: (" - + percentage + "%)"); - processEvent(TRACKING_EVENTS_TYPE.midpoint); - } else if (mQuartile == 3) { - VASTLog.i(TAG, "Video at third quartile: (" - + percentage + "%)"); - processEvent(TRACKING_EVENTS_TYPE.thirdQuartile); + Log.i(TAG, "Video at start: (" + percentage + "%)"); + processEvent(Tracking.START_TYPE); + } + else if (mQuartile == 1) { + Log.i(TAG, "Video at first quartile: (" + percentage + "%)"); + processEvent(Tracking.FIRST_QUARTILE_TYPE); + } + else if (mQuartile == 2) { + Log.i(TAG, "Video at midpoint: (" + percentage + "%)"); + processEvent(Tracking.MIDPOINT_TYPE); + } + else if (mQuartile == 3) { + Log.i(TAG, "Video at third quartile: (" + percentage + "%)"); + processEvent(Tracking.THIRD_QUARTILE_TYPE); stopQuartileTimer(); } mQuartile++; @@ -598,14 +844,14 @@ private void stopQuartileTimer() { private void calculateAspectRatio() { - VASTLog.d(TAG, "entered calculateAspectRatio"); + Log.d(TAG, "entered calculateAspectRatio"); if (mVideoWidth == 0 || mVideoHeight == 0) { - VASTLog.w(TAG, "mVideoWidth or mVideoHeight is 0, skipping calculateAspectRatio"); + Log.w(TAG, "mVideoWidth or mVideoHeight is 0, skipping calculateAspectRatio"); return; } - VASTLog.d(TAG, "calculating aspect ratio"); + Log.d(TAG, "calculating aspect ratio"); double widthRatio = 1.0 * mScreenWidth / mVideoWidth; double heightRatio = 1.0 * mScreenHeight / mVideoHeight; @@ -616,15 +862,14 @@ private void calculateAspectRatio() { FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( surfaceWidth, surfaceHeight); - //params.addRule(RelativeLayout.CENTER_IN_PARENT); mSurfaceView.setLayoutParams(params); mSurfaceHolder.setFixedSize(surfaceWidth, surfaceHeight); - VASTLog.d(TAG, " screen size: " + mScreenWidth + "x" + mScreenHeight); - VASTLog.d(TAG, " video size: " + mVideoWidth + "x" + mVideoHeight); - VASTLog.d(TAG, " widthRatio: " + widthRatio); - VASTLog.d(TAG, " heightRatio: " + heightRatio); - VASTLog.d(TAG, "surface size: " + surfaceWidth + "x" + surfaceHeight); + Log.d(TAG, " screen size: " + mScreenWidth + "x" + mScreenHeight); + Log.d(TAG, " video size: " + mVideoWidth + "x" + mVideoHeight); + Log.d(TAG, " widthRatio: " + widthRatio); + Log.d(TAG, " heightRatio: " + heightRatio); + Log.d(TAG, "surface size: " + surfaceWidth + "x" + surfaceHeight); } } diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/TRACKING_EVENTS_TYPE.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/TRACKING_EVENTS_TYPE.java deleted file mode 100644 index ca03ef5..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/TRACKING_EVENTS_TYPE.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -public enum TRACKING_EVENTS_TYPE { - creativeView, - start, - midpoint, - firstQuartile, - thirdQuartile, - complete, - mute, - unmute, - pause, - rewind, - resume, - fullscreen, - expand, - collapse, - acceptInvitation, - close - -} \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/Tracking.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/Tracking.java deleted file mode 100644 index f1a870e..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/Tracking.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -public class Tracking { - - private String value; - private TRACKING_EVENTS_TYPE event; - - public String getValue() { - - return value; - } - - public void setValue(String value) { - - this.value = value; - } - - public TRACKING_EVENTS_TYPE getEvent() { - - return event; - } - - public void setEvent(TRACKING_EVENTS_TYPE event) { - - this.event = event; - } - - @Override - public String toString() { - - return "Tracking [event=" + event + ", value=" + value + "]"; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTMediaFile.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTMediaFile.java deleted file mode 100644 index 54bff02..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTMediaFile.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -import java.math.BigInteger; - -public class VASTMediaFile { - - private String value; - private String id; - private String delivery; - private String type; - private BigInteger bitrate; - private BigInteger width; - private BigInteger height; - private Boolean scalable; - private Boolean maintainAspectRatio; - private String apiFramework; - - public String getValue() { - - return value; - } - - public void setValue(String value) { - - this.value = value; - } - - public String getId() { - - return id; - } - - public void setId(String value) { - - this.id = value; - } - - public String getDelivery() { - - return delivery; - } - - public void setDelivery(String value) { - - this.delivery = value; - } - - public String getType() { - - return type; - } - - public void setType(String value) { - - this.type = value; - } - - public BigInteger getBitrate() { - - return bitrate; - } - - public void setBitrate(BigInteger value) { - - this.bitrate = value; - } - - public BigInteger getWidth() { - - return width; - } - - public void setWidth(BigInteger value) { - - this.width = value; - } - - public BigInteger getHeight() { - - return height; - } - - public void setHeight(BigInteger value) { - - this.height = value; - } - - public Boolean isScalable() { - - return scalable; - } - - public void setScalable(Boolean value) { - - this.scalable = value; - } - - public Boolean isMaintainAspectRatio() { - - return maintainAspectRatio; - } - - public void setMaintainAspectRatio(Boolean value) { - - this.maintainAspectRatio = value; - } - - public String getApiFramework() { - - return apiFramework; - } - - public void setApiFramework(String value) { - - this.apiFramework = value; - } - - @Override - public String toString() { - - return "MediaFile [value=" + value + ", id=" + id + ", delivery=" - + delivery + ", type=" + type + ", bitrate=" + bitrate - + ", width=" + width + ", height=" + height + ", scalable=" - + scalable + ", maintainAspectRatio=" + maintainAspectRatio - + ", apiFramework=" + apiFramework + "]"; - } - -} \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTModel.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTModel.java deleted file mode 100644 index 53a431b..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VASTModel.java +++ /dev/null @@ -1,401 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -import com.amazon.android.ads.vast.util.VASTLog; -import com.amazon.android.ads.vast.util.XmlTools; - -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathFactory; - -public class VASTModel implements Serializable { - - private static String TAG = "VASTModel"; - - private static final long serialVersionUID = 4318368258447283733L; - - private transient Document vastsDocument; - private String pickedMediaFileURL = null; - - // Tracking xpath expressions - private static final String inlineLinearTrackingXPATH = "/VASTS/VAST/Ad/InLine/Creatives/Creative/Linear/TrackingEvents/Tracking"; - private static final String inlineNonLinearTrackingXPATH = "/VASTS/VAST/Ad/InLine/Creatives/Creative/NonLinearAds/TrackingEvents/Tracking"; - private static final String wrapperLinearTrackingXPATH = "/VASTS/VAST/Ad/Wrapper/Creatives/Creative/Linear/TrackingEvents/Tracking"; - private static final String wrapperNonLinearTrackingXPATH = "/VASTS/VAST/Ad/Wrapper/Creatives/Creative/NonLinearAds/TrackingEvents/Tracking"; - - private static final String combinedTrackingXPATH = inlineLinearTrackingXPATH - + "|" - + inlineNonLinearTrackingXPATH - + "|" - + wrapperLinearTrackingXPATH + "|" + wrapperNonLinearTrackingXPATH; - - // Mediafile xpath expression - private static final String mediaFileXPATH = "//MediaFile"; - - // Duration xpath expression - private static final String durationXPATH = "//Duration"; - - // Videoclicks xpath expression - private static final String videoClicksXPATH = "//VideoClicks"; - - // Videoclicks xpath expression - private static final String impressionXPATH = "//Impression"; - - // Error url xpath expression - private static final String errorUrlXPATH = "//Error"; - - public VASTModel(Document vasts) { - - this.vastsDocument = vasts; - - } - - - public Document getVastsDocument() { - return vastsDocument; - } - - public HashMap> getTrackingUrls() { - VASTLog.d(TAG, "getTrackingUrls"); - - List tracking; - - HashMap> trackings = new HashMap<>(); - - XPath xpath = XPathFactory.newInstance().newXPath(); - - try { - NodeList nodes = (NodeList) xpath.evaluate(combinedTrackingXPATH, - vastsDocument, XPathConstants.NODESET); - Node node; - String trackingURL; - String eventName; - TRACKING_EVENTS_TYPE key; - - if (nodes != null) { - for (int i = 0; i < nodes.getLength(); i++) { - node = nodes.item(i); - NamedNodeMap attributes = node.getAttributes(); - - eventName = (attributes.getNamedItem("event")) - .getNodeValue(); - try { - key = TRACKING_EVENTS_TYPE.valueOf(eventName); - } catch (IllegalArgumentException e) { - VASTLog.w(TAG, "Event:" + eventName - + " is not valid. Skipping it."); - continue; - } - - trackingURL = XmlTools.getElementValue(node); - - if (trackings.containsKey(key)) { - tracking = trackings.get(key); - tracking.add(trackingURL); - } else { - tracking = new ArrayList<>(); - tracking.add(trackingURL); - trackings.put(key, tracking); - - } - - } - } - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - - return trackings; - } - - public List getMediaFiles() { - VASTLog.d(TAG, "getMediaFiles"); - - ArrayList mediaFiles = new ArrayList<>(); - - XPath xpath = XPathFactory.newInstance().newXPath(); - - try { - NodeList nodes = (NodeList) xpath.evaluate(mediaFileXPATH, - vastsDocument, XPathConstants.NODESET); - Node node; - VASTMediaFile mediaFile; - String mediaURL; - Node attributeNode; - - if (nodes != null) { - for (int i = 0; i < nodes.getLength(); i++) { - mediaFile = new VASTMediaFile(); - node = nodes.item(i); - NamedNodeMap attributes = node.getAttributes(); - - attributeNode = attributes.getNamedItem("apiFramework"); - mediaFile.setApiFramework((attributeNode == null) ? null - : attributeNode.getNodeValue()); - - attributeNode = attributes.getNamedItem("bitrate"); - mediaFile.setBitrate((attributeNode == null) ? null - : new BigInteger(attributeNode.getNodeValue())); - - attributeNode = attributes.getNamedItem("delivery"); - mediaFile.setDelivery((attributeNode == null) ? null - : attributeNode.getNodeValue()); - - attributeNode = attributes.getNamedItem("height"); - mediaFile.setHeight((attributeNode == null) ? null - : new BigInteger(attributeNode.getNodeValue())); - - attributeNode = attributes.getNamedItem("id"); - mediaFile.setId((attributeNode == null) ? null - : attributeNode.getNodeValue()); - - attributeNode = attributes - .getNamedItem("maintainAspectRatio"); - mediaFile - .setMaintainAspectRatio((attributeNode == null) ? null - : Boolean.valueOf(attributeNode - .getNodeValue())); - - attributeNode = attributes.getNamedItem("scalable"); - mediaFile.setScalable((attributeNode == null) ? null - : Boolean.valueOf(attributeNode.getNodeValue())); - - attributeNode = attributes.getNamedItem("type"); - mediaFile.setType((attributeNode == null) ? null - : attributeNode.getNodeValue()); - - attributeNode = attributes.getNamedItem("width"); - mediaFile.setWidth((attributeNode == null) ? null - : new BigInteger(attributeNode.getNodeValue())); - - mediaURL = XmlTools.getElementValue(node); - mediaFile.setValue(mediaURL); - - mediaFiles.add(mediaFile); - } - } - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - - return mediaFiles; - } - - public String getDuration() { - VASTLog.d(TAG, "getDuration"); - - String duration = null; - - XPath xpath = XPathFactory.newInstance().newXPath(); - - try { - NodeList nodes = (NodeList) xpath.evaluate(durationXPATH, - vastsDocument, XPathConstants.NODESET); - Node node; - - if (nodes != null) { - for (int i = 0; i < nodes.getLength(); i++) { - node = nodes.item(i); - duration = XmlTools.getElementValue(node); - } - } - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - - return duration; - } - - public VideoClicks getVideoClicks() { - VASTLog.d(TAG, "getVideoClicks"); - - VideoClicks videoClicks = new VideoClicks(); - - XPath xpath = XPathFactory.newInstance().newXPath(); - - try { - NodeList nodes = (NodeList) xpath.evaluate(videoClicksXPATH, - vastsDocument, XPathConstants.NODESET); - Node node; - - if (nodes != null) { - for (int i = 0; i < nodes.getLength(); i++) { - node = nodes.item(i); - - NodeList childNodes = node.getChildNodes(); - - Node child; - String value; - - for (int childIndex = 0; childIndex < childNodes - .getLength(); childIndex++) { - - child = childNodes.item(childIndex); - String nodeName = child.getNodeName(); - - if (nodeName.equalsIgnoreCase("ClickTracking")) { - value = XmlTools.getElementValue(child); - videoClicks.getClickTracking().add(value); - - } else if (nodeName.equalsIgnoreCase("ClickThrough")) { - value = XmlTools.getElementValue(child); - videoClicks.setClickThrough(value); - - } else if (nodeName.equalsIgnoreCase("CustomClick")) { - value = XmlTools.getElementValue(child); - videoClicks.getCustomClick().add(value); - } - } - } - } - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - - return videoClicks; - } - - public List getImpressions() { - VASTLog.d(TAG, "getImpressions"); - - return getListFromXPath(impressionXPATH); - } - - public List getErrorUrl() { - - VASTLog.d(TAG, "getErrorUrl"); - - return getListFromXPath(errorUrlXPATH); - } - - - - private List getListFromXPath(String xPath) { - - VASTLog.d(TAG, "getListFromXPath"); - - ArrayList list = new ArrayList<>(); - - XPath xpath = XPathFactory.newInstance().newXPath(); - - try { - NodeList nodes = (NodeList) xpath.evaluate(xPath, - vastsDocument, XPathConstants.NODESET); - Node node; - - if (nodes != null) { - for (int i = 0; i < nodes.getLength(); i++) { - node = nodes.item(i); - list.add(XmlTools.getElementValue(node)); - } - } - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - - return list; - - } - - - - private void writeObject(ObjectOutputStream oos) throws IOException { - VASTLog.d(TAG, "writeObject: about to write"); - oos.defaultWriteObject(); - - String data = XmlTools.xmlDocumentToString(vastsDocument); - // oos.writeChars(); - oos.writeObject(data); - VASTLog.d(TAG, "done writing"); - - } - - private void readObject(ObjectInputStream ois) - throws ClassNotFoundException, IOException { - VASTLog.d(TAG, "readObject: about to read"); - ois.defaultReadObject(); - - String vastString = (String) ois.readObject(); - VASTLog.d(TAG, "vastString data is:\n" + vastString + "\n"); - - vastsDocument = XmlTools.stringToDocument(vastString); - - VASTLog.d(TAG, "done reading"); - } - - public String getPickedMediaFileURL() { - return pickedMediaFileURL; - } - - public void setPickedMediaFileURL(String pickedMediaFileURL) { - this.pickedMediaFileURL = pickedMediaFileURL; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VAST_DOC_ELEMENTS.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VAST_DOC_ELEMENTS.java deleted file mode 100644 index c03ca52..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VAST_DOC_ELEMENTS.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -public enum VAST_DOC_ELEMENTS { - - vastVersion("2.0"), - vasts("VASTS"), - vastAdTagURI("VASTAdTagURI"), - vastVersionAttribute("version"); - - private String value; - - VAST_DOC_ELEMENTS(String value) { - - this.value = value; - - } - - public String getValue() { - - return value; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VideoClicks.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VideoClicks.java deleted file mode 100644 index 2db6450..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/VideoClicks.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.model; - -import java.util.ArrayList; -import java.util.List; - -public class VideoClicks { - - private String clickThrough; - private List clickTracking; - private List customClick; - - public String getClickThrough() { - - return clickThrough; - } - - public void setClickThrough(String clickThrough) { - - this.clickThrough = clickThrough; - } - - public List getClickTracking() { - - if (clickTracking == null) { - clickTracking = new ArrayList<>(); - } - return this.clickTracking; - } - - public List getCustomClick() { - - if (customClick == null) { - customClick = new ArrayList<>(); - } - return this.customClick; - } - - @Override - public String toString() { - - return "VideoClicks [clickThrough=" + clickThrough - + ", clickTracking=[" + listToString(clickTracking) - + "], customClick=[" + listToString(customClick) + "] ]"; - } - - private String listToString(List list) { - - if (list == null) { - return ""; - } - - StringBuilder sb = new StringBuilder(); - for (int x = 0; x < list.size(); x++) { - sb.append(list.get(x)); - } - return sb.toString(); - } -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Companion.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Companion.java new file mode 100644 index 0000000..80470df --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Companion.java @@ -0,0 +1,185 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.dynamicparser.impl.XmlParser; +import com.amazon.utils.ListUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * A representation of the Companion element of a VAST response. Note: currently only supports + * tracking events. + */ +public class Companion { + + /** + * Key to get the tracking events element. + */ + public static final String TRACKING_EVENTS_KEY = "TrackingEvents"; + + /** + * Key to get the tracking element. + */ + public static final String TRACKING_KEY = "Tracking"; + + /** + * Key to get the width attribute. + */ + private static final String WIDTH_KEY = "width"; + + /** + * Key to get the height attribute. + */ + private static final String HEIGHT_KEY = "height"; + + /** + * List of tracking events. + */ + private List mTrackingEvents; + + /** + * The companion id. + */ + private String mId; + + /** + * The width. + */ + private String mWidth; + + /** + * The height. + */ + private String mHeight; + + /** + * Constructor. + * + * @param companionMap A map containing the data needed to create a companion. + */ + public Companion(Map companionMap) { + + mTrackingEvents = new ArrayList<>(); + Map attributes = companionMap.get(XmlParser.ATTRIBUTES_TAG); + if (attributes != null) { + setId(attributes.get(VmapHelper.ID_KEY)); + setWidth(attributes.get(WIDTH_KEY)); + setHeight(attributes.get(HEIGHT_KEY)); + } + + Map trackingEventsMap = companionMap.get(TRACKING_EVENTS_KEY); + List trackingEventsList = ListUtils.getValueAsMapList(trackingEventsMap, + TRACKING_KEY); + for (Map trackingEventMap : trackingEventsList) { + mTrackingEvents.add(new Tracking(trackingEventMap)); + } + } + + /** + * Get the id. + * + * @return The id. + */ + public String getId() { + + return mId; + } + + /** + * Set the id. + * + * @param id The id. + */ + public void setId(String id) { + + mId = id; + } + + /** + * Get the width. + * + * @return The width. + */ + public String getWidth() { + + return mWidth; + } + + /** + * Set the width. + * + * @param width The width. + */ + public void setWidth(String width) { + + mWidth = width; + } + + /** + * Get the height. + * + * @return The height. + */ + public String getHeight() { + + return mHeight; + } + + /** + * Set the height. + * + * @param height The height. + */ + public void setHeight(String height) { + + mHeight = height; + } + + /** + * Get the list of trackings. + * + * @return List of trackings. + */ + public List getTrackings() { + + return mTrackingEvents; + } + + /** + * Set the trackings. + * + * @param trackings The list of trackings. + */ + public void setTrackings(List trackings) { + + mTrackingEvents = trackings; + } + + @Override + public String toString() { + + return "Companion{" + + "mTrackingEvents=" + mTrackingEvents + + ", mId='" + mId + '\'' + + ", mWidth='" + mWidth + '\'' + + ", mHeight='" + mHeight + '\'' + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/CompanionAd.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/CompanionAd.java new file mode 100644 index 0000000..3473cb0 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/CompanionAd.java @@ -0,0 +1,83 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.utils.ListUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * A representation of the Companion Ad element of a VAST response. + */ +public class CompanionAd extends VastAd { + + private static final String COMPANION_KEY = "Companion"; + + /** + * List of Companion objects. + */ + private List mCompanions; + + /** + * Constructor. + * + * @param map A map containing the data needed to create the Companion Ad. + */ + CompanionAd(Map map) { + + super(); + mCompanions = new ArrayList<>(); + List companionsList = ListUtils.getValueAsMapList(map, COMPANION_KEY); + // Create all the companions. + for (Map companionMap : companionsList) { + Companion companion = new Companion(companionMap); + super.getTrackingEvents().addAll(companion.getTrackings()); + mCompanions.add(companion); + } + } + + /** + * Get the companions. + * + * @return List of companions. + */ + public List getCompanions() { + + return mCompanions; + } + + /** + * Set the companions. + * + * @param companions List of companions. + */ + public void setCompanions(List companions) { + + mCompanions = companions; + } + + + @Override + public String toString() { + + return "CompanionAd{" + + "mCompanions=" + mCompanions + + "mMediaFiles=" + super.getMediaFiles() + + ", mTrackingEvents=" + super.getTrackingEvents() + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Creative.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Creative.java new file mode 100644 index 0000000..c5108c8 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Creative.java @@ -0,0 +1,157 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.dynamicparser.impl.XmlParser; + +import java.util.Map; + +/** + * A representation of the Creative element of a VAST response. Note: does not support the + * Nonlinear element. + */ +public class Creative { + + /** + * Key to get the linear element. + */ + private static final String LINEAR_KEY = "Linear"; + + /** + * Key to get the sequence attribute. + */ + private static final String SEQUENCE_KEY = "sequence"; + + /** + * Key to get the companion ads element. + */ + private static final String COMPANION_ADS_KEY = "CompanionAds"; + + /** + * Key to get the VAST ad id for VAST 2.0 response. + */ + private static final String VAST_2_AD_ID_KEY = "AdID"; + + /** + * The creative id. + */ + private String mId; + + /** + * The sequence. + */ + private String mSequence; + + /** + * The Ad type. Should be either a LinearAd or CompanionAd. + */ + private VastAd mVastAd; + + /** + * Constructor. + * + * @param creativeMap The map that contains the data to create the creative. + */ + public Creative(Map creativeMap) { + + Map attributes = creativeMap.get(XmlParser.ATTRIBUTES_TAG); + if (attributes != null) { + if (attributes.containsKey(VmapHelper.ID_KEY)) { + setId(attributes.get(VmapHelper.ID_KEY)); + } + else { + setId(attributes.get(VAST_2_AD_ID_KEY)); + } + setSequence(attributes.get(SEQUENCE_KEY)); + } + if (creativeMap.containsKey(LINEAR_KEY)) { + mVastAd = new LinearAd(creativeMap.get(LINEAR_KEY)); + } + else if (creativeMap.containsKey(COMPANION_ADS_KEY)) { + mVastAd = new CompanionAd(creativeMap.get(COMPANION_ADS_KEY)); + } + } + + /** + * Get id. + * + * @return The id. + */ + public String getId() { + + return mId; + } + + /** + * Set the id. + * + * @param id The id. + */ + public void setId(String id) { + + mId = id; + } + + /** + * Get the sequence. + * + * @return The sequence. + */ + public String getSequence() { + + return mSequence; + } + + /** + * Set the sequence. + * + * @param sequence The sequence. + */ + public void setSequence(String sequence) { + + mSequence = sequence; + } + + /** + * Get the ad. + * + * @return The ad. + */ + public VastAd getVastAd() { + + return mVastAd; + } + + /** + * Set the ad. + * + * @param vastAd The ad. + */ + public void setVastAd(VastAd vastAd) { + + mVastAd = vastAd; + } + + @Override + public String toString() { + + return "Creative{" + + "mId='" + mId + '\'' + + ", mSequence='" + mSequence + '\'' + + ", mVastAd=" + mVastAd + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Inline.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Inline.java new file mode 100644 index 0000000..0dd0c59 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/Inline.java @@ -0,0 +1,211 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.dynamicparser.impl.XmlParser; +import com.amazon.utils.ListUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A representation of the Inline element of a VAST response. Contains all the files and URIs + * necessary to display the ad. + */ +public class Inline { + + /** + * Key to get the creatives element. + */ + private static final String CREATIVES_KEY = "Creatives"; + + /** + * Key to get the creative element. + */ + private static final String CREATIVE_KEY = "Creative"; + + /** + * The ad id. + */ + private String mId; + + /** + * A list of creatives. + */ + private List mCreatives; + + /** + * A list of impressions. + */ + private List mImpressions; + + /** + * A list of error URLs. + */ + private List mErrorUrls; + + /** + * Constructor. + * + * @param adMap A map containing the data necessary for creating the inline ad. + */ + public Inline(Map adMap) { + + mCreatives = new ArrayList<>(); + mImpressions = new ArrayList<>(); + mErrorUrls = new ArrayList<>(); + + Map attributes = adMap.get(XmlParser.ATTRIBUTES_TAG); + if (attributes != null) { + setId(attributes.get(VmapHelper.ID_KEY)); + } + + if (adMap.containsKey((VmapHelper.INLINE_KEY))) { + Map inLineMap = adMap.get(VmapHelper.INLINE_KEY); + + Map creativesMap = inLineMap.get(CREATIVES_KEY); + if (creativesMap != null) { + List creativeList = ListUtils.getValueAsMapList(creativesMap, CREATIVE_KEY); + + for (Map creativeMap : creativeList) { + mCreatives.add(new Creative(creativeMap)); + } + } + if (inLineMap.containsKey(VmapHelper.IMPRESSION_KEY)) { + mImpressions.addAll(VmapHelper.getStringListFromMap(inLineMap, + VmapHelper.IMPRESSION_KEY)); + } + + if (inLineMap.containsKey(VmapHelper.ERROR_ELEMENT_KEY)) { + mErrorUrls.addAll(VmapHelper.getStringListFromMap(inLineMap, + VmapHelper.ERROR_ELEMENT_KEY)); + } + } + } + + /** + * Get the id. + * + * @return The id. + */ + public String getId() { + + return mId; + } + + /** + * Set the id. + * + * @param id The id. + */ + public void setId(String id) { + + mId = id; + } + + /** + * Get the list of creatives. + * + * @return List of creatives. + */ + public List getCreatives() { + + return mCreatives; + } + + /** + * Set the list of creatives. + * + * @param creatives List of creatives. + */ + public void setCreatives(List creatives) { + + mCreatives = creatives; + } + + /** + * Get the list of impressions. + * + * @return List of impressions. + */ + public List getImpressions() { + + return mImpressions; + } + + /** + * Set the list of impressions. + * + * @param impressions List of impressions. + */ + public void setImpressions(List impressions) { + + mImpressions = impressions; + } + + /** + * Get a list of media files from all the creatives of this ad. + * + * @return List of media files. + */ + public List getMediaFiles() { + + List mediaFiles = new ArrayList<>(); + for (Creative creative : mCreatives) { + mediaFiles.addAll(creative.getVastAd().getMediaFiles()); + } + return mediaFiles; + } + + /** + * Get a map of tracking events from all the creatives of this ad. + * + * @return Map of tracking events. + */ + public HashMap> getTrackingEvents() { + + HashMap> trackingEventMap = new HashMap<>(); + for (Creative creative : mCreatives) { + List trackingList = creative.getVastAd().getTrackingEvents(); + Tracking.addTrackingEventsToMap(trackingEventMap, trackingList); + } + return trackingEventMap; + } + + /** + * Get the list of error URLs. + * + * @return List of error URLs. + */ + public List getErrorUrls() { + + return mErrorUrls; + } + + @Override + public String toString() { + + return "Inline{" + + "mId='" + mId + '\'' + + ", mCreatives=" + mCreatives + + ", mImpressions=" + mImpressions + + ", mErrorUrls=" + mErrorUrls + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/LinearAd.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/LinearAd.java new file mode 100644 index 0000000..93f773c --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/LinearAd.java @@ -0,0 +1,115 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.dynamicparser.impl.XmlParser; +import com.amazon.utils.ListUtils; + +import java.util.List; +import java.util.Map; + +/** + * A representation of a Linear element of a VAST response. Note: Omits parsing the VideoClicks + * element since it doesn't make sense for the TV environment. + */ +public class LinearAd extends VastAd { + + /** + * Key to get MediaFiles element. + */ + private static final String MEDIA_FILES_KEY = "MediaFiles"; + + /** + * Key to get MediaFile element. + */ + private static final String MEDIA_FILE_KEY = "MediaFile"; + + /** + * Key to get TrackingEvents element. + */ + private static final String TRACKING_EVENTS_KEY = "TrackingEvents"; + + /** + * Key to get Duration element. + */ + private static final String DURATION_KEY = "Duration"; + + /** + * The ad duration of the linear creative. "HH:MM.SS.mmm" format. + */ + private String mDuration; + + /** + * Constructor. + * + * @param linearAdMap A map containing the info needed to create the linear ad. + */ + public LinearAd(Map linearAdMap) { + + super(); + + if (linearAdMap != null) { + + Map mediaFilesMap = linearAdMap.get(MEDIA_FILES_KEY); + List mediaFilesList = ListUtils.getValueAsMapList(mediaFilesMap, MEDIA_FILE_KEY); + for (Map mediaFileMap : mediaFilesList) { + super.addMediaFile(new MediaFile(mediaFileMap)); + } + + Map trackingEventsMap = linearAdMap.get(TRACKING_EVENTS_KEY); + List trackingList = + ListUtils.getValueAsMapList(trackingEventsMap, VmapHelper.TRACKING_KEY); + + for (Map trackingMap : trackingList) { + super.addTrackingEvent(new Tracking(trackingMap)); + } + + Map durationMap = linearAdMap.get(DURATION_KEY); + setDuration(durationMap.get(XmlParser.TEXT_TAG)); + } + } + + /** + * Get the duration. + * + * @return The duration. + */ + public String getDuration() { + + return mDuration; + } + + /** + * Set the duration. + * + * @param duration The duration. + */ + public void setDuration(String duration) { + + mDuration = duration; + } + + @Override + public String toString() { + + return "LinearAd{" + + "mDuration='" + mDuration + '\'' + + "mMediaFiles=" + super.getMediaFiles() + + ", mTrackingEvents=" + super.getTrackingEvents() + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/MediaFile.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/MediaFile.java new file mode 100644 index 0000000..af8361b --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/MediaFile.java @@ -0,0 +1,361 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.dynamicparser.impl.XmlParser; + +import java.math.BigInteger; +import java.util.Map; + +/** + * A representation of the MediaFile element of a VAST response. + */ +public class MediaFile { + + /** + * Key to get the maintain aspect ratio attribute. + */ + private static final String MAINTAIN_ASPECT_RATIO_KEY = "maintainAspectRatio"; + + /** + * Key to get the height attribute. + */ + private static final String HEIGHT_KEY = "height"; + + /** + * Key to get the width attribute. + */ + private static final String WIDTH_KEY = "width"; + + /** + * Key to get the bitrate attribute. + */ + private static final String BITRATE_KEY = "bitrate"; + + /** + * Key to get the scalable attribute. + */ + private static final String SCALABLE_KEY = "scalable"; + + /** + * Key to get type attribute. + */ + private static final String TYPE_KEY = "type"; + + /** + * Key to get the delivery attribute. + */ + private static final String DELIVERY_KEY = "delivery"; + + /** + * Key to get the api framework attribute. + */ + private static final String API_FRAMEWORK_KEY = "apiFramework"; + + /** + * The ad URL value. + */ + private String mValue; + + /** + * The id. + */ + private String mId; + + /** + * The delivery protocol. + */ + private String mDelivery; + + /** + * The MIME type for the file container. + */ + private String mType; + + /** + * The average bitrate for the media file. + */ + private BigInteger mBitrate; + + /** + * The native width of the video file, in pixels. + */ + private BigInteger mWidth; + + /** + * The native height of the video file, in pixels. + */ + private BigInteger mHeight; + + /** + * Whether the media file is meant to scale to larger dimensions. + */ + private boolean mScalable; + + /** + * Whether the aspect ratio for media file dimensions should be maintained when scaled to + * larger dimensions. + */ + private boolean mMaintainAspectRatio; + + /** + * The API needed to execute an interactive media file. + */ + private String mApiFramework; + + /** + * Constructor. + * + * @param mediaFileMap A map containing the data needed to create the media file. + */ + public MediaFile(Map mediaFileMap) { + + if (mediaFileMap != null) { + + setValue(VmapHelper.getTextValueFromMap(mediaFileMap)); + Map attributes = mediaFileMap.get(XmlParser.ATTRIBUTES_TAG); + if (attributes != null) { + setId(attributes.get(VmapHelper.ID_KEY)); + setDelivery(attributes.get(DELIVERY_KEY)); + setType(attributes.get(TYPE_KEY)); + setBitrate(BigInteger.valueOf(Long.valueOf(attributes.get(BITRATE_KEY)))); + setWidth(BigInteger.valueOf(Long.valueOf(attributes.get(WIDTH_KEY)))); + setHeight(BigInteger.valueOf(Long.valueOf(attributes.get(HEIGHT_KEY)))); + setScalable(Boolean.valueOf(attributes.get(SCALABLE_KEY))); + setMaintainAspectRatio(Boolean.valueOf(attributes.get(MAINTAIN_ASPECT_RATIO_KEY))); + setApiFramework(attributes.get(API_FRAMEWORK_KEY)); + } + } + } + + /** + * Get the ad URL value. + * + * @return The value. + */ + public String getValue() { + + return mValue; + } + + /** + * Set the value. + * + * @param value The value. + */ + public void setValue(String value) { + + mValue = value; + } + + /** + * Get the id. + * + * @return The id. + */ + public String getId() { + + return mId; + } + + /** + * Set the id. + * + * @param id The id. + */ + public void setId(String id) { + + mId = id; + } + + /** + * Get the delivery. + * + * @return The delivery. + */ + public String getDelivery() { + + return mDelivery; + } + + /** + * Set the delivery. + * + * @param delivery The delivery. + */ + public void setDelivery(String delivery) { + + mDelivery = delivery; + } + + /** + * Get the type. + * + * @return The type. + */ + public String getType() { + + return mType; + } + + /** + * Set the type. + * + * @param type The type. + */ + public void setType(String type) { + + mType = type; + } + + /** + * Get the bitrate. + * + * @return The bitrate. + */ + public BigInteger getBitrate() { + + return mBitrate; + } + + /** + * Set the bitrate. + * + * @param bitrate The bitrate. + */ + public void setBitrate(BigInteger bitrate) { + + mBitrate = bitrate; + } + + /** + * Get the width. + * + * @return The width. + */ + public BigInteger getWidth() { + + return mWidth; + } + + /** + * Set the width. + * + * @param width The width. + */ + public void setWidth(BigInteger width) { + + mWidth = width; + } + + /** + * Get the height. + * + * @return The height. + */ + public BigInteger getHeight() { + + return mHeight; + } + + /** + * Set the height. + * + * @param height The height. + */ + public void setHeight(BigInteger height) { + + mHeight = height; + } + + /** + * Whether the media file is meant to scale to larger dimensions or not. + * + * @return True if it can be scaled, false otherwise. + */ + public boolean isScalable() { + + return mScalable; + } + + /** + * Set the scalable boolean. + * + * @param scalable True if its meant to be scaled, false otherwise. + */ + public void setScalable(boolean scalable) { + + mScalable = scalable; + } + + /** + * Whether the aspect ratio for media file dimensions should be maintained when scaled to new + * dimensions or not. + * + * @return True if the aspect ratio should be maintained, false otherwise. + */ + public boolean isMaintainAspectRatio() { + + return mMaintainAspectRatio; + } + + /** + * Set the maintain aspect ratio boolean. + * + * @param maintainAspectRatio True if the aspect ratio should be maintained, false otherwise. + */ + public void setMaintainAspectRatio(boolean maintainAspectRatio) { + + mMaintainAspectRatio = maintainAspectRatio; + } + + /** + * Get the API framework needed to execute an interactive media file. + * + * @return The API framework. + */ + public String getApiFramework() { + + return mApiFramework; + } + + /** + * Set the API framework needed to execute an interactive media file. + * + * @param apiFramework The API framework + */ + public void setApiFramework(String apiFramework) { + + mApiFramework = apiFramework; + } + + @Override + public String toString() { + + return "MediaFile{" + + "mValue='" + mValue + '\'' + + ", mId='" + mId + '\'' + + ", mDelivery='" + mDelivery + '\'' + + ", mType='" + mType + '\'' + + ", mBitrate=" + mBitrate + + ", mWidth=" + mWidth + + ", mHeight=" + mHeight + + ", mScalable=" + mScalable + + ", mMaintainAspectRatio=" + mMaintainAspectRatio + + ", mApiFramework='" + mApiFramework + '\'' + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastAd.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastAd.java new file mode 100644 index 0000000..09cb791 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastAd.java @@ -0,0 +1,115 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + + +import com.amazon.android.ads.vast.model.vmap.Tracking; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class contains common properties of an ad from within a creative element of a vast response. + */ +public class VastAd { + + /** + * A list of media files. + */ + private List mMediaFiles; + + /** + * A list of tracking events. + */ + private List mTrackingEvents; + + /** + * Constructor. + */ + public VastAd() { + + mMediaFiles = new ArrayList<>(); + mTrackingEvents = new ArrayList<>(); + } + + /** + * Add a media file. + * + * @param mediaFile The media file. + */ + public void addMediaFile(MediaFile mediaFile) { + + mMediaFiles.add(mediaFile); + } + + /** + * Get the media files. + * + * @return List of media files. + */ + public List getMediaFiles() { + + return mMediaFiles; + } + + /** + * Set the media files list. + * + * @param mediaFiles List of media files. + */ + public void setMediaFiles(List mediaFiles) { + + mMediaFiles = mediaFiles; + } + + /** + * Add a tracking event. + * + * @param tracking The tracking event. + */ + public void addTrackingEvent(Tracking tracking) { + + mTrackingEvents.add(tracking); + } + + /** + * Get tracking events. + * + * @return List of tracking events. + */ + public List getTrackingEvents() { + + return mTrackingEvents; + } + + /** + * Set the tracking events. + * + * @param trackingEvents List of tracking events. + */ + public void setTrackingEvents(List trackingEvents) { + + mTrackingEvents = trackingEvents; + } + + @Override + public String toString() { + + return "VastAd{" + + "mMediaFiles=" + mMediaFiles + + ", mTrackingEvents=" + mTrackingEvents + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastResponse.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastResponse.java new file mode 100644 index 0000000..090c585 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vast/VastResponse.java @@ -0,0 +1,349 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vast; + +import com.amazon.ads.IAds; +import com.amazon.android.ads.vast.model.vmap.Tracking; +import com.amazon.android.ads.vast.model.vmap.VmapHelper; +import com.amazon.android.utils.NetworkUtils; +import com.amazon.android.utils.PathHelper; +import com.amazon.dynamicparser.IParser; +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class represents a VAST response. + */ +public class VastResponse { + + private static final String TAG = VastResponse.class.getSimpleName(); + + /** + * Value for VAST version 3. + */ + public static final String VAST_3 = "vast3"; + + /** + * Value for VAST version 2. + */ + public static final String VAST_2 = "vast2"; + + /** + * Value for VAST version 1. + */ + public static final String VAST_1 = "vast1"; + + /** + * Another value for VAST version 1. + */ + public static final String VAST = "vast"; + + /** + * Key to get the version attribute. + */ + private static final String VERSION_KEY = "version"; + + /** + * Key to get the ad element. + */ + private static final String AD_KEY = "Ad"; + + /** + * Key to get the wrapper element. + */ + private static final String WRAPPER_ELEMENT_KEY = "Wrapper"; + + /** + * Key to get the VAST ad tag URI element. + */ + private static final String VASTADTAGURI_ELEMENT_KEY = "VASTAdTagURI"; + + /** + * Path for the TrackingEvents element. + */ + private static final String TRACKING_EVENTS_PATH = "Creatives/Creative/Linear/TrackingEvents"; + + /** + * The official version with which the response is complaint. + */ + private String mVersion; + + /** + * The Inline element. + */ + private List mInlineAds; + + /** + * Constructor. + */ + public VastResponse() { + + mInlineAds = new ArrayList<>(); + } + + /** + * Creates a instance of a VAST response given the parsed xml data. + * + * @param xmlMap A map representing the parsed xml response. + * @return An VAST response instance. + */ + public static VastResponse createInstance(Map xmlMap) { + + VastResponse vastResponse = new VastResponse(); + Log.d(TAG, "Creating VAST response from xml map"); + + Map attributes = xmlMap.get(XmlParser.ATTRIBUTES_TAG); + + if (attributes != null) { + vastResponse.setVersion(attributes.get(VERSION_KEY)); + Object adElementObject = xmlMap.get(AD_KEY); + if (adElementObject == null) { + Log.d(TAG, "No ad found in vast response"); + return vastResponse; + } + + if (adElementObject instanceof List) { + for (Map map : (List>) adElementObject) { + processAdElement(map, vastResponse); + } + } + else { + processAdElement((Map) adElementObject, vastResponse); + } + } + return vastResponse; + } + + /** + * Helper method to process ad element. It will either process the wrapper withing the ad + * element or create inlines (whichever is available in the map). + * + * @param map The map containing the ad element data to process. + * @param vastResponse The original VAST response object. + */ + private static void processAdElement(Map map, VastResponse vastResponse) { + + if (map.containsKey(WRAPPER_ELEMENT_KEY)) { + processWrapper(map.get(WRAPPER_ELEMENT_KEY), vastResponse); + } + else { + vastResponse.getInlineAds().add(new Inline(map)); + } + } + + /** + * Process the wrapper element. Creates a VastResponse from the VASTAdTagURI element and then + * ads the wrapper's impression URL, error URL, and linear tracking events to the Inline ads of + * the VastResponse. + * + * @param wrapperMap The map containing the wrapper data. + * @param vastResponse The original VAST response object. + */ + private static void processWrapper(Map wrapperMap, VastResponse + vastResponse) { + + // Process the VastAdTagURI to get wrapped VAST response. + String vastAdTagUri = VmapHelper.getTextValueFromMap(wrapperMap.get + (VASTADTAGURI_ELEMENT_KEY)); + VastResponse wrappedVastResponse = createInstance(vastAdTagUri); + + // Add the creative's tracking events from the wrapper to the inline ad. + Map trackingEventsMap = + PathHelper.getMapByPath((Map) wrapperMap, TRACKING_EVENTS_PATH); + + // Add the impression, error url, and tracking events to each creative of each inline add. + for (Inline inline : wrappedVastResponse.getInlineAds()) { + for (Map trackingMap : (List) trackingEventsMap.get(VmapHelper.TRACKING_KEY)) { + for (Creative creative : inline.getCreatives()) { + creative.getVastAd().addTrackingEvent(new Tracking(trackingMap)); + } + } + // Add the impression to the inline + if (wrapperMap.containsKey(VmapHelper.IMPRESSION_KEY)) { + inline.getImpressions().addAll( + VmapHelper.getStringListFromMap(wrapperMap, VmapHelper.IMPRESSION_KEY)); + } + // Add the error url to the inline + if (wrapperMap.containsKey(VmapHelper.ERROR_ELEMENT_KEY)) { + inline.getErrorUrls().addAll( + VmapHelper.getStringListFromMap(wrapperMap, VmapHelper.ERROR_ELEMENT_KEY)); + } + + // Add the inline ad to the original vast response object. + vastResponse.getInlineAds().add(inline); + } + } + + /** + * Create an instance given an ad tag URL. Downloads the data from the URL, parses it, and + * creates the VAST response object based on the parsed data. + * + * Note: This method should be called off the UI thread. + * + * @param adTag The URL to get the VAST response from. + * @return A VAST response instance. + */ + public static VastResponse createInstance(String adTag) { + + Log.d(TAG, "Processing ad url string for vast model"); + String xmlData; + try { + adTag = NetworkUtils.addParameterToUrl(adTag, IAds.CORRELATOR_PARAMETER, + "" + System.currentTimeMillis()); + xmlData = NetworkUtils.getDataLocatedAtUrl(adTag); + } + catch (IOException e) { + Log.e(TAG, "Could not get data from url " + adTag, e); + return null; + } + + XmlParser parser = new XmlParser(); + try { + Map xmlMap = (Map) parser.parse(xmlData); + + if (xmlMap != null) { + Log.d(TAG, "Wrapping VAST response with VMAP"); + return VastResponse.createInstance(xmlMap); + } + } + catch (IParser.InvalidDataException e) { + Log.e(TAG, "Data could not be parsed. ", e); + } + Log.e(TAG, "Error creating vast model from ad tag."); + return null; + } + + /** + * Get the version. + * + * @return The version. + */ + public String getVersion() { + + return mVersion; + } + + /** + * Set the version. + * + * @param version The version. + */ + public void setVersion(String version) { + + mVersion = version; + } + + /** + * Get the inline ads. + * + * @return The inline ads. + */ + public List getInlineAds() { + + return mInlineAds; + } + + /** + * Set the inline ads. + * + * @param inlineAds The inline ads. + */ + public void setInlineAds(List inlineAds) { + + mInlineAds = inlineAds; + } + + /** + * Gets all the media files from the inline. + * + * @return List of media files. + */ + public List getMediaFiles() { + + ArrayList mediaFiles = new ArrayList<>(); + for (Inline inline : mInlineAds) { + + mediaFiles.addAll(inline.getMediaFiles()); + } + return mediaFiles; + } + + /** + * Gets all the impressions from the inline. + * + * @return List of impression strings. + */ + public List getImpressions() { + + List impressions = new ArrayList<>(); + for (Inline inline : mInlineAds) { + + impressions.addAll(inline.getImpressions()); + } + return impressions; + } + + /** + * Gets all the tracking URLs from the inline. + * + * @return Map of tracking events and their corresponding URLs. + */ + public Map> getTrackingUrls() { + + Map> sortedMap = new HashMap<>(); + for (Inline inline : mInlineAds) { + HashMap> map = inline.getTrackingEvents(); + for (String key : map.keySet()) { + if (sortedMap.containsKey(key)) { + sortedMap.get(key).addAll(map.get(key)); + } + else { + sortedMap.put(key, map.get(key)); + } + } + } + return sortedMap; + } + + /** + * Get the error URLs from the inline. + * + * @return List of error URLs. + */ + public List getErrorUrls() { + + List errorUrls = new ArrayList<>(); + for (Inline inline : mInlineAds) { + errorUrls.addAll(inline.getErrorUrls()); + } + return errorUrls; + } + + @Override + public String toString() { + + return "VastResponse{" + + "mVersion='" + mVersion + '\'' + + ", mInlineAds=" + mInlineAds + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdBreak.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdBreak.java new file mode 100644 index 0000000..ab49eb0 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdBreak.java @@ -0,0 +1,444 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.android.ads.vast.model.vast.MediaFile; +import com.amazon.dynamicparser.impl.XmlParser; +import com.amazon.utils.DateAndTimeHelper; +import com.amazon.utils.ListUtils; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a single ad break. Each ad break may have multiple ads. + */ +public class AdBreak { + + private static final String TAG = AdBreak.class.getSimpleName(); + + /** + * Key to get the extensions element. + */ + public static final String EXTENSIONS_KEY = "vmap:Extensions"; + + /** + * Key to get the extension element. + */ + public static final String EXTENSION_KEY = "vmap:Extension"; + + /** + * Key to get the time offset attribute. + */ + private static final String TIME_OFFSET_ATTR = "timeOffset"; + + /** + * Key to get the break type attribute. + */ + private static final String BREAK_TYPE_ATTR = "breakType"; + + /** + * Key to get the break id attribute. + */ + private static final String BREAK_ID_ATTR = "breakId"; + + /** + * Key to get the repeat after attribute. + */ + private static final String REPEAT_AFTER_ATTR = "repeatAfter"; + + /** + * Key to get the ad source element. + */ + private static final String AD_SOURCE_KEY = "vmap:AdSource"; + + /** + * Key to get the tracking events element. + */ + private static final String TRACKING_EVENTS_KEY = "vmap:TrackingEvents"; + + /** + * Key to get the tracking event element. + */ + private static final String TRACKING_EVENT_KEY = "vmap:Tracking"; + + /** + * Value to represent that the ad should be played at the start of the content. + */ + public static final String TIME_OFFSET_START = "start"; + + /** + * Value to represent that the ad should be played at the end of the content. + */ + public static final String TIME_OFFSET_END = "end"; + + /** + * Value to represent a linear break type. + */ + public static final String BREAK_TYPE_LINEAR = "linear"; + + /** + * hh:mm:ss.mmm, "start", "end", n% (n is an integer from 0-100), #m (m represents sequence + * and is an integer > 0) + */ + private String mTimeOffset; // required + + /** + * Suggested hint to the player. + */ + private String mBreakType; // required + + /** + * ID of the ad break. + */ + private String mBreakId; // optional + + /** + * Optional indicator that instructs the video player to repeat the same AdBreak and AdSource + * at time offsets equal to the duration value of this attribute. Expressed in time format + * HH.MM.SS[.mmm] + */ + private String mRepeatAfter; // optional + + /** + * Represents the ad data that will be used to fill the ad break. + */ + private AdSource mAdSource; // optional + + /** + * The tracking events. + */ + private List mTrackingEvents; // optional + + /** + * Container for Extensions that provide ability to express information not supported by VMAP. + */ + private List mExtensions; // optional + + /** + * The selected media file. This will be used to display the ad video. + */ + private String mSelectedMediaFileUrl; + + /** + * Constructor. + */ + public AdBreak() { + + } + + /** + * Constructor. + * + * @param adBreakMap A map containing the data required to create an ad break. + */ + public AdBreak(Map adBreakMap) { + + if (adBreakMap == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributeMap = + (Map) adBreakMap.get(XmlParser.ATTRIBUTES_TAG); + + setTimeOffset(attributeMap.get(TIME_OFFSET_ATTR)); + setBreakType(attributeMap.get(BREAK_TYPE_ATTR)); + setBreakId(attributeMap.get(BREAK_ID_ATTR)); + setRepeatAfter(attributeMap.get(REPEAT_AFTER_ATTR)); + + Map adSourceMap = (Map) adBreakMap.get(AD_SOURCE_KEY); + setAdSource(new AdSource(adSourceMap)); + + mExtensions = new ArrayList<>(); + Map extensionsMap = (Map) adBreakMap.get(VmapHelper + .EXTENSIONS_KEY); + if (extensionsMap != null) { + mExtensions.addAll(VmapHelper.createExtensions(extensionsMap)); + } + + mTrackingEvents = new ArrayList<>(); + Map trackingEventsMap = (Map) adBreakMap.get(TRACKING_EVENTS_KEY); + if (trackingEventsMap != null) { + createTrackingEvents(trackingEventsMap); + } + } + + /** + * Get the time offset. + * + * @return The time offset. + */ + + public String getTimeOffset() { + + return mTimeOffset; + } + + /** + * Set the time offset. + * + * @param timeOffset The time offset. + */ + public void setTimeOffset(String timeOffset) { + + mTimeOffset = timeOffset; + } + + /** + * Get the break type. + * + * @return The break type. + */ + public String getBreakType() { + + return mBreakType; + } + + /** + * Set the break type. + * + * @param breakType The break type. + */ + public void setBreakType(String breakType) { + + mBreakType = breakType; + } + + /** + * Get the break id. + * + * @return The break id. + */ + public String getBreakId() { + + return mBreakId; + } + + /** + * Set the break id. + * + * @param breakId The break id. + */ + public void setBreakId(String breakId) { + + mBreakId = breakId; + } + + /** + * Get the repeat after option. + * + * @return The repeat after option. + */ + public String getRepeatAfter() { + + return mRepeatAfter; + } + + /** + * Set the repeat after option. + * + * @param repeatAfter The repeat after option. + */ + public void setRepeatAfter(String repeatAfter) { + + mRepeatAfter = repeatAfter; + } + + /** + * Get the ad source. + * + * @return The ad source. + */ + public AdSource getAdSource() { + + return mAdSource; + } + + /** + * Set the ad source. + * + * @param adSource The ad source. + */ + public void setAdSource(AdSource adSource) { + + mAdSource = adSource; + } + + /** + * Get the tracking events. + * + * @return The tracking events. + */ + public List getTrackingEvents() { + + return mTrackingEvents; + } + + /** + * Set the tracking events. + * + * @param trackingEvents The tracking events. + */ + public void setTrackingEvents(List trackingEvents) { + + mTrackingEvents = trackingEvents; + } + + /** + * Get the extensions. + * + * @return The extensions. + */ + public List getExtensions() { + + return mExtensions; + } + + /** + * Set the extensions. + * + * @param extensions The extensions. + */ + public void setExtensions(List extensions) { + + mExtensions = extensions; + } + + /** + * Get the time offset converted to seconds. + * + * @return Time offset in seconds. + */ + public double getConvertedTimeOffset() { + + // TODO: handle other time offsets (DEVTECH-4139) + return DateAndTimeHelper.convertDateFormatToSeconds(mTimeOffset); + } + + /** + * Get the selected media file URL. + * + * @return The selected media file URL. + */ + public String getSelectedMediaFileUrl() { + + return mSelectedMediaFileUrl; + } + + /** + * Set the selected media file URL. This URL will be used to play the video. + * + * @param selectedMediaFileUrl The selected media file URL. + */ + public void setSelectedMediaFileUrl(String selectedMediaFileUrl) { + + mSelectedMediaFileUrl = selectedMediaFileUrl; + } + + /** + * Get the media files from the ad source. + * + * @return List of media files. + */ + public List getMediaFiles() { + + List mediaFiles = new ArrayList<>(); + if (mAdSource != null && mAdSource.getVastResponse() != null) { + mediaFiles.addAll(mAdSource.getVastResponse().getMediaFiles()); + } + + return mediaFiles; + } + + /** + * Get the impressions from the ad source. + * + * @return List of the impression strings. + */ + public List getImpressions() { + + List impressions = new ArrayList<>(); + if (mAdSource != null && mAdSource.getVastResponse() != null) { + impressions.addAll(mAdSource.getVastResponse().getImpressions()); + } + + return impressions; + } + + /** + * Get the tracking URLs from the ad source. + * + * @return List of tracking URLs. + */ + public HashMap> getTrackingUrls() { + + HashMap> trackingUrls = new HashMap<>(); + + if (getTrackingEvents() != null && getTrackingEvents().size() > 0) { + Tracking.addTrackingEventsToMap(trackingUrls, getTrackingEvents()); + } + else if (getAdSource().getVastResponse() != null) { + trackingUrls.putAll(getAdSource().getVastResponse().getTrackingUrls()); + } + + return trackingUrls; + } + + /** + * Get the error URLs from the ad source. + * + * @return List of error URLs. + */ + public List getErrorUrls() { + + return null; + } + + /** + * Creates the tracking events for the ad break. + * + * @param trackingEventsMap Data map containing the info needed to create the tracking events. + */ + private void createTrackingEvents(Map trackingEventsMap) { + + List trackingEventsList = ListUtils.getValueAsMapList + (trackingEventsMap, TRACKING_EVENT_KEY); + + for (Map trackingMap : trackingEventsList) { + mTrackingEvents.add(new Tracking(trackingMap)); + } + + } + + @Override + public String toString() { + + return "AdBreak{" + + "mTimeOffset='" + mTimeOffset + '\'' + + ", mBreakType='" + mBreakType + '\'' + + ", mBreakId='" + mBreakId + '\'' + + ", mRepeatAfter='" + mRepeatAfter + '\'' + + ", mAdSource=" + mAdSource + + ", mTrackingEvents=" + mTrackingEvents + + ", mExtensions=" + mExtensions + + ", mSelectedMediaFileUrl='" + mSelectedMediaFileUrl + '\'' + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdSource.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdSource.java new file mode 100644 index 0000000..da6e694 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdSource.java @@ -0,0 +1,293 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.android.ads.vast.model.vast.VastResponse; +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.util.Map; + +/** + * Provides the player with either an inline ad response or reference to an ad response. The ad + * response should be in the form of one of the following: VAST ad response, custom ad response, or + * ad tag URI. + */ +public class AdSource { + + private static final String TAG = AdSource.class.getSimpleName(); + + /** + * Key to get the allow multiple ads attribute. + */ + private static final String ALLOW_MULTIPLE_ADS_KEY = "allowMultipleAds"; + + /** + * Key to get the follow redirects attribute. + */ + private static final String FOLLOW_REDIRECTS_KEY = "followRedirects"; + + /** + * Key to get the ad tag uri element. + */ + private static final String AD_TAG_URI_KEY = "vmap:AdTagURI"; + + /** + * Key to get the custom data element. + */ + private static final String CUSTOM_AD_DATA_KEY = "vmap:CustomAdData"; + + /** + * Key to get the VAST ad data element. + */ + private static final String VAST_AD_DATA_KEY = "vmap:VASTAdData"; + + /** + * The ad source id. + */ + private String mId; // optional + + /** + * Indicates whether the player should select and play only a single ad from the ad response + * document, or play multiple ads. If not specified, left to the video player's discretion. + * Non-VAST ad pods may be ignored. + */ + private boolean mAllowMultipleAds = true; // optional + + /** + * Whether the player should follow wrappers/redirects in the ad response document. If not + * specified, left to the video player’s discretion + */ + private boolean mFollowRedirects; // optional + + /** + * A VAST 3.0 document that comprises the ad response document. Not contained within a CDATA. + */ + private VastResponse mVastResponse; + + /** + * An ad response document that is not VAST 3.0. + */ + private CustomAdData mCustomAdData; + + /** + * A URL to a secondary ad server that will provide the ad response document. + */ + private AdTagURI mAdTagURI; + + /** + * Constructor. + */ + public AdSource() { + + } + + /** + * Constructor. + * + * @param adSourceMap A map containing the data needed to create the ad source object. + */ + public AdSource(Map adSourceMap) { + + if (adSourceMap == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributeMap = + (Map) adSourceMap.get(XmlParser.ATTRIBUTES_TAG); + + setId(attributeMap.get(VmapHelper.ID_KEY)); + setAllowMultipleAds(Boolean.valueOf(attributeMap.get(ALLOW_MULTIPLE_ADS_KEY))); + setFollowRedirects(Boolean.valueOf(attributeMap.get(FOLLOW_REDIRECTS_KEY))); + + if (adSourceMap.get(AD_TAG_URI_KEY) != null) { + Map adTagUriMap = (Map) adSourceMap.get(AD_TAG_URI_KEY); + // Check that the ad tag URI is for a VAST template. If so, parse the VAST ad tag URI. + if (isAdTagUriVast(adTagUriMap)) { + String adTag = VmapHelper.getTextValueFromMap(adSourceMap.get(AD_TAG_URI_KEY)); + setVastResponse(VastResponse.createInstance(adTag)); + } + else { + setAdTagURI(new AdTagURI(adSourceMap.get(AD_TAG_URI_KEY))); + } + } + else if (adSourceMap.get(CUSTOM_AD_DATA_KEY) != null) { + setCustomAdData(new CustomAdData(adSourceMap.get(CUSTOM_AD_DATA_KEY))); + } + else if (adSourceMap.get(VAST_AD_DATA_KEY) != null) { + setVastResponse(VastResponse.createInstance(adSourceMap.get(VAST_AD_DATA_KEY))); + } + } + + /** + * Checks if the ad tag URI is for a VAST response. + * + * @param adTagUriMap Map containing data for ad tag URI. + * @return True if the ad tag URI is for VAST, false otherwise. + */ + private boolean isAdTagUriVast(Map adTagUriMap) { + + Map attributes = adTagUriMap.get(XmlParser.ATTRIBUTES_TAG); + return !(attributes == null || attributes.get(VmapHelper.TEMPLATE_TYPE_KEY) == null) && + isVastTemplate(attributes.get(VmapHelper.TEMPLATE_TYPE_KEY)); + } + + /** + * Test whether or not the template is for an accepted VAST response. + * + * @param template The template to test. + * @return True if the template matches an accepted VAST response; false otherwise. + */ + private boolean isVastTemplate(String template) { + + return template.equals(VastResponse.VAST_3) || template.equals(VastResponse.VAST_2) + || template.equals(VastResponse.VAST_1) || template.equals(VastResponse.VAST); + } + + /** + * Get the ad source id. + * + * @return The ad source id. + */ + public String getId() { + + return mId; + } + + /** + * Set the add source id. + * + * @param id The ad source id. + */ + public void setId(String id) { + + mId = id; + } + + /** + * Get the allow multiple ads value. + * + * @return True if allowing multiple ads has been specified. + */ + public boolean isAllowMultipleAds() { + + return mAllowMultipleAds; + } + + /** + * Set the allow multiple ads value. + * + * @param allowMultipleAds The allow multiple ads value. + */ + public void setAllowMultipleAds(boolean allowMultipleAds) { + + mAllowMultipleAds = allowMultipleAds; + } + + /** + * Get the follow redirects value. + * + * @return True if the follow redirects value has been specified. + */ + public boolean isFollowRedirects() { + + return mFollowRedirects; + } + + /** + * Set the follow redirects value. + * + * @param followRedirects The follow redirects value. + */ + public void setFollowRedirects(boolean followRedirects) { + + mFollowRedirects = followRedirects; + } + + /** + * Get the VAST response that contains the ad response document. + * + * @return The VAST response. + */ + public VastResponse getVastResponse() { + + return mVastResponse; + } + + /** + * Set the VAST response. + * + * @param vastResponse The VAST response. + */ + public void setVastResponse(VastResponse vastResponse) { + + mVastResponse = vastResponse; + } + + /** + * Get the custom ad data. + * + * @return The custom ad data. + */ + public CustomAdData getCustomAdData() { + + return mCustomAdData; + } + + /** + * Set the custom ad data. + * + * @param customAdData The custom ad data. + */ + public void setCustomAdData(CustomAdData customAdData) { + + mCustomAdData = customAdData; + } + + /** + * Get the ad tag URI. + * + * @return The ad tag URI. + */ + public AdTagURI getAdTagURI() { + + return mAdTagURI; + } + + /** + * Set the ad tag URI. + * + * @param adTagURI The ad tag URI. + */ + public void setAdTagURI(AdTagURI adTagURI) { + + mAdTagURI = adTagURI; + } + + @Override + public String toString() { + + return "AdSource{" + + "mId='" + mId + '\'' + + ", mAllowMultipleAds=" + mAllowMultipleAds + + ", mFollowRedirects=" + mFollowRedirects + + ", mVastResponse=" + mVastResponse + + ", mCustomAdData=" + mCustomAdData + + ", mAdTagURI=" + mAdTagURI + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdTagURI.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdTagURI.java new file mode 100644 index 0000000..238b34a --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/AdTagURI.java @@ -0,0 +1,107 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.util.Map; + +/** + * A URL to a secondary ad server that will provide the ad response document. + */ +public class AdTagURI { + + private static final String TAG = AdTagURI.class.getSimpleName(); + + /** + * The URL to a secondary ad server that will provide the ad response document. + */ + private String mUri; + + /** + * The ad response template employed by thee ad response document. + */ + private String mTemplateType; + + /** + * Constructor. + * + * @param map A map containing the data needed to create the ad tag URI. + */ + public AdTagURI(Map map) { + + if (map == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributeMap = (Map) map.get(XmlParser.ATTRIBUTES_TAG); + if (attributeMap != null) { + setTemplateType(attributeMap.get(VmapHelper.TEMPLATE_TYPE_KEY)); + } + setUri(VmapHelper.getTextValueFromMap(map)); + } + + /** + * Get the URI. + * + * @return The URI. + */ + public String getUri() { + + return mUri; + } + + /** + * Set the URI. + * + * @param uri The URI. + */ + public void setUri(String uri) { + + mUri = uri; + } + + /** + * Get the template type. + * + * @return The template type. + */ + public String getTemplateType() { + + return mTemplateType; + } + + /** + * Set the template type. + * + * @param templateType The template type. + */ + public void setTemplateType(String templateType) { + + mTemplateType = templateType; + } + + @Override + public String toString() { + + return "AdTagURI{" + + "mUri='" + mUri + '\'' + + ", mTemplateType='" + mTemplateType + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/CustomAdData.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/CustomAdData.java new file mode 100644 index 0000000..936f953 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/CustomAdData.java @@ -0,0 +1,108 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.util.Map; + +/** + * An ad response document that is not VAST 3.0 + */ +public class CustomAdData { + + private static final String TAG = CustomAdData.class.getSimpleName(); + + /** + * Arbitrary string data that represents non-VAST ad response. + */ + private String mCustomAdResponse; + + /** + * The ad response template employed by the ad response document. + */ + private String mTemplateType; + + /** + * Constructor. + * + * @param map A map containing the data needed to create the custom data object. + */ + public CustomAdData(Map map) { + + if (map == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributeMap = (Map) map.get(XmlParser.ATTRIBUTES_TAG); + if (attributeMap != null) { + setTemplateType(attributeMap.get(VmapHelper.TEMPLATE_TYPE_KEY)); + } + + setCustomAdResponse(VmapHelper.getTextValueFromMap(map)); + } + + /** + * Get the custom ad response. + * + * @return The custom ad response. + */ + public String getCustomAdResponse() { + + return mCustomAdResponse; + } + + /** + * Set the custom ad response. + * + * @param customAdResponse The custom ad response. + */ + public void setCustomAdResponse(String customAdResponse) { + + mCustomAdResponse = customAdResponse; + } + + /** + * Get the template type. + * + * @return The template type. + */ + public String getTemplateType() { + + return mTemplateType; + } + + /** + * Set the template type. + * + * @param templateType The template type. + */ + public void setTemplateType(String templateType) { + + mTemplateType = templateType; + } + + @Override + public String toString() { + + return "CustomAdData{" + + "mCustomAdResponse='" + mCustomAdResponse + '\'' + + ", mTemplateType='" + mTemplateType + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Extension.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Extension.java new file mode 100644 index 0000000..470df7b --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Extension.java @@ -0,0 +1,118 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.util.Map; + +/** + * Part of the element. Proprietary XML data, expressed in a unique namespace. + */ +public class Extension { + + private static final String TAG = Extension.class.getSimpleName(); + + /** + * Key for getting the suppress bumper attribute. + */ + private static final String SUPPRESS_BUMPER_KEY = "suppress_bumper"; + + /** + * Key for getting the type attribute. + */ + private static final String TYPE_KEY = "type"; + + /** + * The XML content of the Extension. + */ + private boolean mSuppressBumper; + + /** + * The type of the extension. The type value must be globally unique. A URI is recommended + */ + private String mType; + + /** + * Constructor. + * + * @param extensionMap A map containing the data needed to create the extension. + */ + public Extension(Map extensionMap) { + + if (extensionMap == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributesMap = + (Map) extensionMap.get(XmlParser.ATTRIBUTES_TAG); + + setSuppressBumper(Boolean.valueOf(attributesMap.get(SUPPRESS_BUMPER_KEY))); + setType(attributesMap.get(TYPE_KEY)); + } + + /** + * Get the type of the extension. + * + * @return The extension type. + */ + public String getType() { + + return mType; + } + + /** + * Set the type of the extension. + * + * @param type The extension type. + */ + public void setType(String type) { + + mType = type; + } + + /** + * Get the XML content value. + * + * @return The XML content. + */ + public boolean isSuppressBumper() { + + return mSuppressBumper; + } + + /** + * Set the XML content suppressBumper. + * + * @param suppressBumper The XML content. + */ + + public void setSuppressBumper(boolean suppressBumper) { + + mSuppressBumper = suppressBumper; + } + + @Override + public String toString() { + + return "Extension{" + + "mSuppressBumper=" + mSuppressBumper + + ", mType='" + mType + '\'' + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Tracking.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Tracking.java new file mode 100644 index 0000000..b48534d --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/Tracking.java @@ -0,0 +1,239 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Part of the element. Provides a tracking URI that is used to provide tracking + * URIs for the three tracking events available in VMAP. + */ +public class Tracking { + + private static final String TAG = Tracking.class.getSimpleName(); + + /** + * This event indicates that an individual creative portion of the ad was viewed. + */ + public static final String CREATE_VIEW_TYPE = "createView"; + + /** + * This event is used to indicate that an individual creative within the ad was loaded and + * playback began. + */ + public static final String START_TYPE = "start"; + + /** + * The creative played for at least 50% of the total duration. + */ + public static final String MIDPOINT_TYPE = "midpoint"; + + /** + * The creative played for at least 25% of the total duration. + */ + public static final String FIRST_QUARTILE_TYPE = "firstQuartile"; + + /** + * The creative played for at least 75% of the total duration + */ + public static final String THIRD_QUARTILE_TYPE = "thirdQuartile"; + + /** + * The creative was played to the end at a normal speed. + */ + public static final String COMPLETE_TYPE = "complete"; + + /** + * The user activated the mute control and muted the creative. + */ + public static final String MUTE_TYPE = "mute"; + + /** + * The user activated the mute control and unmuted the creative. + */ + public static final String UNMUTE_TYPE = "unmute"; + + /** + * The user clicked teh pause control and stopped the creative. + */ + public static final String PAUSE_TYPE = "pause"; + + /** + * The user activated the rewind control to access a previous point in the creative timeline. + */ + public static final String REWIND_TYPE = "rewind"; + + /** + * The user activated a control to extend the video player to the edges of the viewer's screen. + */ + public static final String RESUME_TYPE = "resume"; + + /** + * The user activated the control to extend the video player to the edges of the viewer's + * screen. + */ + public static final String FULL_SCREEN_TYPE = "fullscreen"; + + /** + * The user activated the control to reduce video player size to original dimensions. + */ + public static final String EXIT_FULL_SCREEN_TYPE = "exitFullScreen"; + + /** + * The user activated a control to expand the creative. + */ + public static final String EXPAND_TYPE = "expand"; + + /** + * The user activated a control to reduce the creative to its original dimensions. + */ + public static final String COLLAPSE_TYPE = "collapse"; + + /** + * The user activated a control that launched an additional portion of the creative. + */ + public static final String ACCEPT_INVITATION_TYPE = "acceptInvitationLinear"; + + /** + * The user clicked the close button on the creative. + */ + public static final String CLOSE_TYPE = "closeLinear"; + + /** + * The user activated a skip control to skip the creative. + */ + public static final String SKIP_TYPE = "skip"; + + /** + * The creative played for a duration at normal speed that is equal or greater than the value + * provided in an additional attribute for offset. + */ + public static final String PROGRESS_TYPE = "progress"; + + /** + * Key to find the event attribute from the map. + */ + private static final String EVENT_KEY = "event"; + + /** + * URI to track for specific event type. + */ + private String mUri; + + /** + * The name of the VMAP ad break level event to track. + */ + private String mEvent; + + /** + * Constructor. + * + * @param map A data map that contains the URI data and an attributes map. + */ + public Tracking(Map map) { + + if (map == null) { + Log.e(TAG, "Data map for constructing ad source cannot be null"); + throw new IllegalArgumentException("Data map parameter cannot be null"); + } + + Map attributes = map.get(XmlParser.ATTRIBUTES_TAG); + if (attributes != null) { + setEvent(attributes.get(EVENT_KEY)); + } + + setUri(VmapHelper.getTextValueFromMap(map)); + } + + /** + * Get the URI. + * + * @return The URI. + */ + public String getUri() { + + return mUri; + } + + /** + * Set the URI. + * + * @param uri The URI. + */ + public void setUri(String uri) { + + mUri = uri; + } + + /** + * Get the event type. + * + * @return The event type. + */ + public String getEvent() { + + return mEvent; + } + + /** + * Set the event type. + * + * @param event The event type. + */ + public void setEvent(String event) { + + mEvent = event; + } + + /** + * Helper method that sorts tracking events into a map by event type. + * + * @param trackingEventMap The map to put the sorted tracking events into. + * @param trackingList The list of tracking events to sort. + * @return The sorted map. + */ + public static HashMap> addTrackingEventsToMap( + HashMap> trackingEventMap, List trackingList) { + + for (Tracking event : trackingList) { + if (trackingEventMap.containsKey(event.getEvent())) { + trackingEventMap.get(event.getEvent()).add(event.getUri()); + } + else { + List uris = new ArrayList<>(); + uris.add(event.getUri()); + trackingEventMap.put(event.getEvent(), uris); + } + } + return trackingEventMap; + } + + + @Override + public String toString() { + + return "Tracking{" + + "mUri='" + mUri + '\'' + + ", mEvent='" + mEvent + '\'' + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapHelper.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapHelper.java new file mode 100644 index 0000000..84757e2 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapHelper.java @@ -0,0 +1,132 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.dynamicparser.impl.XmlParser; +import com.amazon.utils.ListUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This class contains common keys to use while parsing a VMAP/VAST model. + */ +public class VmapHelper { + + /** + * Key to get the extensions element. + */ + public static final String EXTENSIONS_KEY = "vmap:Extensions"; + + /** + * Key to get the extension element. + */ + public static final String EXTENSION_KEY = "vmap:Extension"; + + /** + * Key to get the inline element. + */ + public static final String INLINE_KEY = "InLine"; + + /** + * Key to get the id attribute. + */ + public static final String ID_KEY = "id"; + + /** + * Key to get the template type attribute. + */ + public static final String TEMPLATE_TYPE_KEY = "templateType"; + + /** + * Key to get the error element. + */ + public static final String ERROR_ELEMENT_KEY = "Error"; + + /** + * Key to get the impression element. + */ + public static final String IMPRESSION_KEY = "Impression"; + + /** + * Key to get Tracking element. + */ + public static final String TRACKING_KEY = "Tracking"; + + /** + * Creates a list of extensions from the given data map. + * + * @param extensionsMap Data map containing the info needed to create the extensions. + * @return List of extensions. + */ + public static List createExtensions(Map extensionsMap) { + + List extensions = new ArrayList<>(); + + Object extensionObject = extensionsMap.get(VmapHelper.EXTENSION_KEY); + if (extensionObject instanceof List) { + + List extensionList = + ListUtils.getValueAsMapList(extensionsMap, VmapHelper.EXTENSION_KEY); + + for (Map extensionMap : extensionList) { + extensions.add(new Extension(extensionMap)); + } + } + else { + Map extensionMap = (Map) extensionObject; + if (extensionMap != null) { + extensions.add(new Extension(extensionMap)); + } + } + return extensions; + } + + /** + * Tries to get a text value from the map. It first tries to get the value using the cdata key. + * If no CDATA value is found, it returns the value from the map using the text key. + * + * @param map The map containing the text value. + * @return The text value returned by the cdata or text key. + */ + public static String getTextValueFromMap(Map map) { + + if (map.containsKey(XmlParser.CDATA_TAG)) { + return String.valueOf(map.get(XmlParser.CDATA_TAG)); + } + return String.valueOf(map.get(XmlParser.TEXT_TAG)); + } + + /** + * Tries to get a list of strings from the value that matches to the key within the map. + * + * @param map The map containing the strings. + * @param key The key. + * @return List of strings. + */ + public static List getStringListFromMap(Map map, String key) { + + List strings = new ArrayList<>(); + List impressionList = ListUtils.getValueAsMapList(map, key); + for (Map impressionMap : impressionList) { + String impression = VmapHelper.getTextValueFromMap(impressionMap); + if (impression != null) { + strings.add(impression); + } + } + return strings; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapResponse.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapResponse.java new file mode 100644 index 0000000..f96be8b --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/model/vmap/VmapResponse.java @@ -0,0 +1,224 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.model.vmap; + +import com.amazon.ads.IAds; +import com.amazon.android.ads.vast.model.vast.VastResponse; +import com.amazon.utils.ListUtils; + +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * This class represents a parsed VMAP response and follows selected parts of the VMAP 1.0 Schema as + * presented by IAB here: https://www.iab.com/wp-content/uploads/2015/06/VMAP.pdf + */ +public class VmapResponse { + + private static final String TAG = VmapResponse.class.getSimpleName(); + + /** + * Key to find the ad break element. + */ + private static final String AD_BREAK_KEY = "vmap:AdBreak"; + + /** + * List of ad breaks. + */ + private List mAdBreaks; + + /** + * The ad that the + */ + private AdBreak mCurrentAd; + + /** + * Constructor. + */ + public VmapResponse() { + + mAdBreaks = new ArrayList<>(); + } + + /** + * Creates an instance of the {@link VmapResponse} class with the given xml data. The xml should + * follow the official VMAP schema as described here: + * https://www.iab.com/wp-content/uploads/2015/06/VMAP.pdf + * + * @param xmlMap The xml data. + * @return A model object or null if there were instantiation errors. + */ + public static VmapResponse createInstance(Map xmlMap) { + + VmapResponse model = new VmapResponse(); + Log.d(TAG, "Creating VMAP model from xml data"); + + if (xmlMap != null) { + + // All VMAP models must have at least one ad break, return null if none are found. + if (xmlMap.get(AD_BREAK_KEY) == null) { + Log.e(TAG, "VMAP model xml contains no ad break element"); + return null; + } + List adBreakMapList = ListUtils.getValueAsMapList(xmlMap, AD_BREAK_KEY); + for (Map adBreakMap : adBreakMapList) { + model.getAdBreaks().add(new AdBreak(adBreakMap)); + } + } + model.setCurrentAd(model.getAdBreaks().get(0)); + + return model; + } + + /** + * Create a VMAP response with the given VAST response. It will set the VAST response ad as a + * linear pre-roll ad to be played at the start. + * + * @param vastResponse The VAST ad response. + * @return Teh VMAP response. + */ + public static VmapResponse createInstanceWithVast(VastResponse vastResponse) { + + VmapResponse vmapResponse = new VmapResponse(); + + // Create an Ad Break to store the pre-roll ad. + AdBreak adBreak = new AdBreak(); + adBreak.setBreakId(IAds.PRE_ROLL_AD); + adBreak.setTimeOffset(AdBreak.TIME_OFFSET_START); + adBreak.setBreakType(AdBreak.BREAK_TYPE_LINEAR); + + // Create an Ad Source to store the VAST ad. + AdSource adSource = new AdSource(); + adSource.setVastResponse(vastResponse); + + adBreak.setAdSource(adSource); + + vmapResponse.addAdBreak(adBreak); + + return vmapResponse; + } + + /** + * Get the ad breaks. + * + * @return The ad breaks. + */ + public List getAdBreaks() { + + return mAdBreaks; + } + + /** + * Set the ad breaks. + * + * @param adBreaks The ad breaks. + */ + public void setAdBreaks(List adBreaks) { + + mAdBreaks = adBreaks; + } + + /** + * Add an ad break. + * + * @param adBreak The ad break. + */ + public void addAdBreak(AdBreak adBreak) { + + if (mAdBreaks == null) { + mAdBreaks = new ArrayList<>(); + } + mAdBreaks.add(adBreak); + } + + /** + * Get all the ads with "preroll" in their ids. + * + * @return List of ads. + */ + public List getPreRollAdBreaks() { + + return getAdBreaksForType(IAds.PRE_ROLL_AD); + } + + /** + * Get all the ads with "midroll" in their ids. + * + * @return List of ads. + */ + public List getMidRollAdBreaks() { + + return getAdBreaksForType(IAds.MID_ROLL_AD); + } + + /** + * Get all the ads with "postroll" in their ids. + * + * @return List of ids. + */ + public List getPostRollAdBreaks() { + + return getAdBreaksForType(IAds.POST_ROLL_AD); + } + + /** + * Get a list of ad breaks for the given type of ad. + * + * @param type The type of ad: preroll, midroll, or postroll. + * @return List of ad breaks. + */ + private List getAdBreaksForType(String type) { + + List ads = new ArrayList<>(); + for (AdBreak ad : mAdBreaks) { + if (ad.getBreakId().contains(type)) { + ads.add(ad); + } + } + return ads; + } + + /** + * Get the current ad to play. + * + * @return The current ad. + */ + public AdBreak getCurrentAd() { + + return mCurrentAd; + } + + /** + * Set the current ad to play. + * + * @param currentAd The current ad. + */ + public void setCurrentAd(AdBreak currentAd) { + + mCurrentAd = currentAd; + } + + @Override + public String toString() { + + return "VmapResponse{" + + "mAdBreaks=" + mAdBreaks + + ", mCurrentAd=" + mCurrentAd + + '}'; + } +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/AdTagProcessor.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/AdTagProcessor.java new file mode 100644 index 0000000..231d628 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/AdTagProcessor.java @@ -0,0 +1,141 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.processor; + +import com.amazon.android.ads.vast.model.vast.VastResponse; +import com.amazon.android.ads.vast.model.vmap.VmapResponse; +import com.amazon.android.utils.NetworkUtils; +import com.amazon.dynamicparser.IParser; +import com.amazon.dynamicparser.impl.XmlParser; + +import android.util.Log; + +import java.io.IOException; +import java.util.Map; + +/** + * This class contains the logic to process a VMAP ad tag. It will read the data from the URL and + * attempt to parse it into the {@link VmapResponse} class. If the tag does not follow the proper + * VMAP schema, an error is returned. See the following for documentation: + * https://www.iab.com/wp-content/uploads/2015/06/VMAP.pdf + */ +public class AdTagProcessor { + + private static final String TAG = AdTagProcessor.class.getSimpleName(); + + /** + * Constant used to test for VMAP response. + */ + private static final String XMLNS_VMAP_KEY = "xmlns:vmap"; + + /** + * The type of the ad. Currently only VMAP, or errors. + */ + public enum AdTagType { + vmap, + vast, + error, + error_model_creation, + validation_error + } + + /** + * The VMAP response instance. + */ + private VmapResponse mVmapResponse; + + /** + * The media file picker. + */ + private MediaPicker mMediaPicker; + + /** + * Constructor. + * + * @param mediaPicker The media file picker. Used to validate the VMAP response after + * processing the ad tag URL. + */ + public AdTagProcessor(MediaPicker mediaPicker) { + + mMediaPicker = mediaPicker; + } + + /** + * Processes the data from a VMAP ad URL into a {@link VmapResponse} object. + * + * @param urlString The URL to process. + * @return The {@link AdTagType#vmap} value if a model was successfully created, otherwise an + * error value. + */ + public AdTagType process(String urlString) { + + Log.d(TAG, "Processing ad url string"); + String xmlData; + AdTagType type; + try { + xmlData = NetworkUtils.getDataLocatedAtUrl(urlString); + } + catch (IOException e) { + Log.e(TAG, "Could not get data from url " + urlString, e); + return AdTagType.error; + } + + XmlParser parser = new XmlParser(); + try { + Map xmlMap = (Map) parser.parse(xmlData); + + if (xmlMap != null) { + Map attributes = xmlMap.get(XmlParser.ATTRIBUTES_TAG); + + try { + if (attributes != null && attributes.containsKey(XMLNS_VMAP_KEY)) { + mVmapResponse = VmapResponse.createInstance(xmlMap); + type = AdTagType.vmap; + } + else { + Log.d(TAG, "Converting VAST response into VMAP response"); + VastResponse vastResponse = VastResponse.createInstance(xmlMap); + mVmapResponse = VmapResponse.createInstanceWithVast(vastResponse); + type = AdTagType.vast; + } + if (!ResponseValidator.validate(mVmapResponse, mMediaPicker)) { + return AdTagType.validation_error; + } + return type; + } + catch (IllegalArgumentException e) { + Log.e(TAG, "Caught IllegalArgumentException.", e); + return AdTagType.error_model_creation; + } + } + } + catch (IParser.InvalidDataException e) { + Log.e(TAG, "Data could not be parsed. ", e); + } + + return AdTagType.error; + } + + /** + * Get the ad response. + * + * @return The VMAP ad response. + */ + public VmapResponse getAdResponse() { + + return mVmapResponse; + } + +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTMediaPicker.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/MediaPicker.java similarity index 94% rename from VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTMediaPicker.java rename to VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/MediaPicker.java index e8d9d18..fc82a9f 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTMediaPicker.java +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/MediaPicker.java @@ -44,13 +44,13 @@ */ package com.amazon.android.ads.vast.processor; -import com.amazon.android.ads.vast.model.VASTMediaFile; +import com.amazon.android.ads.vast.model.vast.MediaFile; import java.util.List; -public interface VASTMediaPicker { +public interface MediaPicker { - VASTMediaFile pickVideo(List list); + MediaFile pickVideo(List list); } diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/ResponseValidator.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/ResponseValidator.java new file mode 100644 index 0000000..1131144 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/ResponseValidator.java @@ -0,0 +1,140 @@ +/** + * This file was modified by Amazon: + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +/* + * Copyright (c) 2014, Nexage, Inc. All rights reserved. + * Copyright (C) 2016 Amazon Inc. + * + * Provided under BSD-3 license as follows: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.amazon.android.ads.vast.processor; + +import com.amazon.android.ads.vast.model.vast.MediaFile; +import com.amazon.android.ads.vast.model.vmap.AdBreak; +import com.amazon.android.ads.vast.model.vmap.VmapResponse; + +import android.text.TextUtils; +import android.util.Log; + +import java.util.List; + +/** + * Validates a VMAP response. + */ +public class ResponseValidator { + + private static final String TAG = ResponseValidator.class.getSimpleName(); + + /** + * Validate if the VMAP response with the given media picker. + * + * @param response The VMAP ad response. + * @param mediaPicker The media file picker. + * @return True if the response is valid, false otherwise. + */ + public static boolean validate(VmapResponse response, MediaPicker mediaPicker) { + + boolean isValid = false; + + Log.d(TAG, "validating ad response."); + + // We need at least one media file to play for the VMAP response to be valid. + for (AdBreak adBreak : response.getAdBreaks()) { + + // Must have a MediaPicker to choose one of the MediaFile element from XML + if (mediaPicker != null) { + MediaFile mediaFile = mediaPicker.pickVideo(adBreak.getMediaFiles()); + + if (mediaFile != null) { + String url = mediaFile.getValue(); + if (!TextUtils.isEmpty(url)) { + isValid = true; + // Set the best media file to use when playing the ad. + adBreak.setSelectedMediaFileUrl(url); + Log.d(TAG, "mediaPicker selected mediaFile with URL " + url); + } + } + } + else { + Log.e(TAG, "A MediaPicker is necessary to validate ad response."); + return false; + } + } + + Log.d(TAG, "Validator returns: " + (isValid ? "valid" : "not valid (no media file)")); + + return isValid; + } + + /** + * Validate the ad break. To be valid, the ad break must have at least one impression and at + * least one media file. + * + * @param adBreak The ad to validate. + * @return True if the ad break is valid, false otherwise. + */ + public static boolean validateAdBreak(AdBreak adBreak) { + + Log.d(TAG, "Validating ad break."); + + // Validate that there is an ad source + if (adBreak.getAdSource().getAdTagURI() == null && + adBreak.getAdSource().getCustomAdData() == null && + adBreak.getAdSource().getVastResponse() == null) { + Log.d(TAG, "Validator error: ad break needs a valid ad source"); + return false; + } + + // There should be at least one impression. + List impressions = adBreak.getImpressions(); + if (impressions == null || impressions.size() == 0) { + Log.d(TAG, "Validator error: impressions list invalid"); + return false; + } + + // There must be at least one media file. + List mediaFiles = adBreak.getMediaFiles(); + if (mediaFiles == null || mediaFiles.size() == 0) { + Log.d(TAG, "Validator error: mediaFile list invalid"); + return false; + } + + return true; + } + +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTModelPostValidator.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTModelPostValidator.java deleted file mode 100644 index 13e7471..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTModelPostValidator.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.processor; - -import com.amazon.android.ads.vast.util.VASTLog; -import com.amazon.android.ads.vast.model.VASTMediaFile; -import com.amazon.android.ads.vast.model.VASTModel; - -import android.text.TextUtils; - -import java.util.List; - -public class VASTModelPostValidator { - - private static final String TAG = "VASTModelPostValidator"; - - - // This method tries to make sure that there is at least 1 Media file to - // be used for VASTActivity. Also, if the boolean validateModel is true, it will - // do additional validations which includes "at least 1 impression tracking url's is required' - // If any of the above fails, it returns false. The false indicates that you can stop proceeding - // further to display this on the MediaPlayer. - - public static boolean validate(VASTModel model, VASTMediaPicker mediaPicker) { - - VASTLog.d(TAG, "validate"); - - if (!validateModel(model)) { - VASTLog.d(TAG, "Validator returns: not valid (invalid model)"); - return false; - } - - boolean isValid = false; - - // Must have a MediaPicker to choose one of the MediaFile element from XML - if (mediaPicker != null) { - List mediaFiles = model.getMediaFiles(); - VASTMediaFile mediaFile = mediaPicker.pickVideo(mediaFiles); - - if (mediaFile != null) { - String url = mediaFile.getValue(); - if (!TextUtils.isEmpty(url)) { - isValid = true; - // Let's set this value inside VASTModel so that it can be - // accessed from VASTPlayer - model.setPickedMediaFileURL(url); - VASTLog.d(TAG, - "mediaPicker selected mediaFile with URL " + url); - } - } - - } - else { - VASTLog.w(TAG, "mediaPicker: We don't have a compatible media file to play."); - } - - VASTLog.d(TAG, "Validator returns: " + (isValid ? "valid" : "not valid (no media file)")); - - return isValid; - } - - - private static boolean validateModel(VASTModel model) { - - VASTLog.d(TAG, "validateModel"); - boolean isValid = true; - - // There should be at least one impression. - List impressions = model.getImpressions(); - if (impressions == null || impressions.size() == 0) { - isValid = false; - } - - // There must be at least one VASTMediaFile object - List mediaFiles = model.getMediaFiles(); - if (mediaFiles == null || mediaFiles.size() == 0) { - VASTLog.d(TAG, "Validator error: mediaFile list invalid"); - isValid = false; - } - - return isValid; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTProcessor.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTProcessor.java deleted file mode 100644 index b9c91d4..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/VASTProcessor.java +++ /dev/null @@ -1,256 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.processor; - -import com.amazon.android.ads.vast.VASTAdsPlayer; -import com.amazon.android.ads.vast.model.VASTModel; -import com.amazon.android.ads.vast.model.VAST_DOC_ELEMENTS; -import com.amazon.android.ads.vast.util.VASTLog; -import com.amazon.android.ads.vast.util.XmlTools; -import com.amazon.android.ads.vast.util.XmlValidation; - -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.nio.charset.Charset; - -import javax.xml.parsers.DocumentBuilderFactory; - -/** - * This class is responsible for taking a VAST 2.0 XML file, parsing it, - * validating it, and creating a valid VASTModel object corresponding to it. - * - * It can handle "regular" VAST XML files as well as VAST wrapper files. - */ -public final class VASTProcessor { - - private static final String TAG = "VASTProcessor"; - - // Maximum number of VAST files that can be read (wrapper file(s) + actual - // target file) - private static final int MAX_VAST_LEVELS = 5; - private static final boolean IS_VALIDATION_ON = false; - - private VASTMediaPicker mediaPicker; - private VASTModel vastModel; - private StringBuilder mergedVastDocs = new StringBuilder(500); - - public VASTProcessor(VASTMediaPicker mediaPicker) { - - this.mediaPicker = mediaPicker; - } - - public VASTModel getModel() { - - return vastModel; - } - - public int process(String xmlData) { - - VASTLog.d(TAG, "process"); - vastModel = null; - InputStream is; - - - try { - is = new ByteArrayInputStream(xmlData.getBytes(Charset - .defaultCharset().name())); - } - catch (UnsupportedEncodingException e) { - VASTLog.e(TAG, e.getMessage(), e); - return VASTAdsPlayer.ERROR_XML_PARSE; - } - - int error = processUri(is, 0); - try { - is.close(); - } - catch (IOException e) { - VASTLog.e(TAG, "Failed to close input stream", e); - } - if (error != VASTAdsPlayer.ERROR_NONE) { - return error; - } - - Document mainDoc = wrapMergedVastDocWithVasts(); - vastModel = new VASTModel(mainDoc); - - if (mainDoc == null) { - return VASTAdsPlayer.ERROR_XML_PARSE; - } - - - if (!VASTModelPostValidator.validate(vastModel, mediaPicker)) { - return VASTAdsPlayer.ERROR_POST_VALIDATION; - } - - return VASTAdsPlayer.ERROR_NONE; - } - - private Document wrapMergedVastDocWithVasts() { - - VASTLog.d(TAG, "wrapmergedVastDocWithVasts"); - mergedVastDocs.insert(0, ""); - mergedVastDocs.append(""); - - String merged = mergedVastDocs.toString(); - VASTLog.v(TAG, "Merged VAST doc:\n" + merged); - - return XmlTools.stringToDocument(merged); - } - - private int processUri(InputStream is, int depth) { - - VASTLog.d(TAG, "processUri"); - - if (depth >= MAX_VAST_LEVELS) { - String message = "VAST wrapping exceeded max limit of " - + MAX_VAST_LEVELS + "."; - VASTLog.e(TAG, message); - return VASTAdsPlayer.ERROR_EXCEEDED_WRAPPER_LIMIT; - } - - Document doc = createDoc(is); - if (doc == null) { - return VASTAdsPlayer.ERROR_XML_PARSE; - } - - if (IS_VALIDATION_ON) { - if (!validateAgainstSchema(doc)) { - return VASTAdsPlayer.ERROR_SCHEMA_VALIDATION; - } - } - - merge(doc); - - // check to see if this is a VAST wrapper ad - NodeList uriToNextDoc = doc - .getElementsByTagName(VAST_DOC_ELEMENTS.vastAdTagURI.getValue()); - if (uriToNextDoc == null || uriToNextDoc.getLength() == 0) { - // This isn't a wrapper ad, so we're done. - return VASTAdsPlayer.ERROR_NONE; - } - else { - // This is a wrapper ad, so move on to the wrapped ad and process - // it. - VASTLog.d(TAG, "Doc is a wrapper. "); - Node node = uriToNextDoc.item(0); - String nextUri = XmlTools.getElementValue(node); - VASTLog.d(TAG, "Wrapper URL: " + nextUri); - InputStream nextInputStream; - try { - URL nextUrl = new URL(nextUri); - nextInputStream = nextUrl.openStream(); - } - catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return VASTAdsPlayer.ERROR_XML_OPEN_OR_READ; - } - int error = processUri(nextInputStream, depth + 1); - try { - nextInputStream.close(); - } - catch (IOException e) { - VASTLog.e(TAG, "Failed to close input stream", e); - } - return error; - } - } - - - private Document createDoc(InputStream is) { - - VASTLog.d(TAG, "About to create doc from InputStream"); - try { - Document doc = DocumentBuilderFactory.newInstance() - .newDocumentBuilder().parse(is); - doc.getDocumentElement().normalize(); - VASTLog.d(TAG, "Doc successfully created."); - return doc; - } - catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return null; - } - } - - private void merge(Document newDoc) { - - VASTLog.d(TAG, "About to merge doc into main doc."); - - NodeList nl = newDoc.getElementsByTagName("VAST"); - - Node newDocElement = nl.item(0); - - String doc = XmlTools.xmlDocumentToString(newDocElement); - mergedVastDocs.append(doc); - - VASTLog.d(TAG, "Merge successful."); - } - - // Validator using mfXerces..... - private boolean validateAgainstSchema(Document doc) { - - VASTLog.d(TAG, "About to validate doc against schema."); - InputStream stream = VASTProcessor.class - .getResourceAsStream("assets/vast_2_0_1_schema.xsd"); - String xml = XmlTools.xmlDocumentToString(doc); - boolean isValid = XmlValidation.validate(stream, xml); - try { - stream.close(); - } - catch (IOException e) { - VASTLog.e(TAG, "Failed to close input stream", e); - } - return isValid; - } - -} \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast3_draft.xsd b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast3_draft.xsd new file mode 100644 index 0000000..430743c --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast3_draft.xsd @@ -0,0 +1,1036 @@ + + + + + IAB VAST, Video Ad Serving Template, video xml ad response, Version 3.0.0, xml schema prepared by Google + + + + + + Top-level element, wraps each ad in the response + + + + + + Second-level element surrounding complete ad data for a single ad + + + + + + Indicates source ad server + + + + + Common name of ad + + + + + Longer description of ad + + + + + Common name of advertiser + + + + + The price of the ad. + + + + + + + The pricing model used. + + + + + + + + + + + + + The currency of the pricing. + + + + + + + + + + + + + + URL of request to survey vendor + + + + + URL to request if ad does not play due to error + + + + + + Contains all creative elements within an InLine or Wrapper Ad + + + + + + Wraps each creative element within an InLine or Wrapper Ad + + + + + + + + + + + + Any number of icons representing advertising industry initiatives. + + + + + + + + + Duration in standard time format, hh:mm:ss + + + + + + Data to be passed into the video ad + + + + + + + + + Location of linear file + + + + + + + Optional identifier + + + + + Method of delivery of ad + + + + + + + + + + + MIME type. Popular MIME types include, but are not limited to “video/x-ms-wmv” for Windows Media, and “video/x-flv” for Flash Video. Image ads or interactive ads can be included in the MediaFiles section with appropriate Mime + types + + + + + Bitrate of encoded video in Kbps. If bitrate is supplied, minBitrate and maxBitrate should not be supplied. + + + + + Minimum bitrate of an adaptive stream in Kbps. If minBitrate is supplied, maxBitrate must be supplied and bitrate should not be supplied. + + + + + Maximum bitrate of an adaptive stream in Kbps. If maxBitrate is supplied, minBitrate must be supplied and bitrate should not be supplied. + + + + + Pixel dimensions of video + + + + + Pixel dimensions of video + + + + + Whether it is acceptable to scale the image. + + + + + Whether the ad must have its aspect ratio maintained when scales + + + + + The apiFramework defines the method to use for communication if the MediaFile is interactive. Suggested values for this element are “VPAID”, “FlashVars” (for Flash/Flex), “initParams” (for Silverlight) and “GetVariables” + (variables placed in key/value pairs on the asset request). + + + + + The codec used to produce the media file. + + + + + + + + + + + + + The time at which the ad becomes skippable, if absent, the ad is not skippable. + + + + + + + + + + + + + + + Any number of companions in any desired pixel dimensions. + + + + + + Provides information about which companion creative to display. All means that the player must attempt to display all. Any means the player must attempt to play at least one. None means all companions are optional. + + + + + + + + + + + + + + + + + + Any number of companions in any desired pixel dimensions. + + + + + + + + + + The preferred order in which multiple Creatives should be displayed + + + + + Ad-ID for the creative (formerly ISCI) + + + + + + + + + + + + + + Second-level element surrounding wrapper ad pointing to Secondary ad server. + + + + + + Indicates source ad server + + + + + URL of ad tag of downstream Secondary Ad Server + + + + + URL to request if ad does not play due to error + + + + + URL to request to track an impression + + + + + + + + + + + + + + + + + + Any number of icons representing advertising industry initiatives. + + + + + + + + + + + + URL to request for tracking purposes when user clicks on the video + + + + + + + + + + + + URLs to request on custom events such as hotspotted video + + + + + + + + + + + + + + + + + + + + + Definition of Companion ad, if served separately + + + + + + + + + + + + Any number of companions in any desired pixel dimensions. + + + + + + + + + + The preferred order in which multiple Creatives should be displayed + + + + + Ad-ID for the creative (formerly ISCI) + + + + + + + + + + + + + + + + Identifies the sequence of multiple Ads and defines an Ad Pod. + + + + + + + + Current version is 3.0. + + + + + + + + + The name of the event to track for the element. The creativeView should always be requested when present. + + + + + + + The name of the event to track. For nonlinear ads these events should be recorded on the video within the ad. + + + + + + + + + + + + + + + + + + + + + + + + + + + + The time during the video at which this url should be pinged. Must be present for progress event. + + + + + + + + + + + + + + + + + + URL to open as destination page when user clicks on the video + + + + + + + + + + + + URL to request for tracking purposes when user clicks on the video + + + + + + + + + + + + URLs to request on custom events such as hotspotted video + + + + + + + + + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + Mime type of static resource + + + + + + + + + URL source for an IFrame to display the companion element + + + + + HTML to display the companion element + + + + + + + The creativeView should always be requested when present. For Companions creativeView is the only supported event. + + + + + URL to open as destination page when user clicks on the the companion banner ad. + + + + + Alt text to be displayed when companion is rendered in HTML environment. + + + + + Data to be passed into the companion ads. The apiFramework defines the method to use for communication (e.g. “FlashVar”) + + + + + + Optional identifier + + + + + Pixel dimensions of companion slot + + + + + Pixel dimensions of companion slot + + + + + Pixel dimensions of the companion asset + + + + + Pixel dimensions of the companion asset + + + + + Pixel dimensions of expanding companion ad when in expanded state + + + + + Pixel dimensions of expanding companion ad when in expanded state + + + + + The apiFramework defines the method to use for communication with the companion + + + + + Used to match companion creative to publisher placement areas on the page. + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + Mime type of static resource + + + + + + + + + URL source for an IFrame to display the companion element + + + + + HTML to display the companion element + + + + + + + The creativeView should always be requested when present. For Companions creativeView is the only supported event. + + + + + URL to open as destination page when user clicks on the the companion banner ad. + + + + + URLs to ping when user clicks on the the companion banner ad. + + + + + Alt text to be displayed when companion is rendered in HTML environment. + + + + + Data to be passed into the companion ads. The apiFramework defines the method to use for communication (e.g. “FlashVar”) + + + + + + Optional identifier + + + + + Pixel dimensions of companion slot + + + + + Pixel dimensions of companion slot + + + + + Pixel dimensions of the companion asset + + + + + Pixel dimensions of the companion asset + + + + + Pixel dimensions of expanding companion ad when in expanded state + + + + + Pixel dimensions of expanding companion ad when in expanded state + + + + + The apiFramework defines the method to use for communication with the companion + + + + + Used to match companion creative to publisher placement areas on the page. + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + Mime type of static resource + + + + + + + + + URL source for an IFrame to display the companion element + + + + + HTML to display the companion element + + + + + + + URLs to ping when user clicks on the the non-linear ad. + + + + + URL to open as destination page when user clicks on the non-linear ad unit. + + + + + Data to be passed into the video ad. + + + + + + Optional identifier + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of expanding nonlinear ad when in expanded state + + + + + Pixel dimensions of expanding nonlinear ad when in expanded state + + + + + Whether it is acceptable to scale the image. + + + + + Whether the ad must have its aspect ratio maintained when scales + + + + + Suggested duration to display non-linear ad, typically for animation to complete. Expressed in standard time format hh:mm:ss + + + + + The apiFramework defines the method to use for communication with the nonlinear element + + + + + + + + + URLs to ping when user clicks on the the non-linear ad unit. + + + + + + Optional identifier + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of expanding nonlinear ad when in expanded state + + + + + Pixel dimensions of expanding nonlinear ad when in expanded state + + + + + Whether it is acceptable to scale the image. + + + + + Whether the ad must have its aspect ratio maintained when scales + + + + + Suggested duration to display non-linear ad, typically for animation to complete. Expressed in standard time format hh:mm:ss + + + + + The apiFramework defines the method to use for communication with the nonlinear element + + + + + + + + + Internal version used by ad system + + + + + + + + + + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + Mime type of static resource + + + + + + + + + URL source for an IFrame to display the companion element + + + + + HTML to display the companion element + + + + + + + + + URLs to ping when user clicks on the the icon. + + + + + URL to open as destination page when user clicks on the icon. + + + + + + + + URLs to ping when icon is shown. + + + + + + Identifies the industry initiative that the icon supports. + + + + + Pixel dimensions of icon. + + + + + Pixel dimensions of icon. + + + + + The horizontal alignment location (in pixels) or a specific alignment. + + + + + + + + + + The vertical alignment location (in pixels) or a specific alignment. + + + + + + + + + + Start time at which the player should display the icon. Expressed in standard time format hh:mm:ss. + + + + + The duration for which the player must display the icon. Expressed in standard time format hh:mm:ss. + + + + + The apiFramework defines the method to use for communication with the icon element + + + + + + + + Any valid XML may be included in the Extensions node + + + + + + + + + + + + + + + Any valid XML may be included in the Extensions node + + + + + + + + + + + + + + + + Specifies whether the parameters are XML-encoded + + + + + + + + + + + Specifies whether the HTML is XML-encoded + + + + + + \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast_2_0_1_schema.xsd b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast_2_0_1_schema.xsd index 0f73731..6407b80 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast_2_0_1_schema.xsd +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vast_2_0_1_schema.xsd @@ -1,643 +1,990 @@ - - - IAB VAST, Video Ad Serving Template, video xml ad response, Version 2.0.1, xml schema prepared by Tremor Media - - - - - - Top-level element, wraps each ad in the response - - - - - - Second-level element surrounding complete ad data for a single ad - - - - - - Indicates source ad server - - - - - Common name of ad - - - - - Longer description of ad - - - - - URL of request to survey vendor - - - - - URL to request if ad does not play due to error - - - - - URL to track impression - - - - - Any number of companions in any desired pixel dimensions. - - - - - - Wraps each creative element within an InLine or Wrapper Ad - - - - - - - - - Duration in standard time format, hh:mm:ss - - - - - - Data to be passed into the video ad - - - - - - - - - Location of linear file - - - - - - - Optional identifier - - - - - Method of delivery of ad - - - - - - - - - - - MIME type. Popular MIME types include, but are not limited to “video/x-ms-wmv” for Windows Media, and “video/x-flv” for Flash Video. Image ads or interactive ads can be included in the MediaFiles section with appropriate Mime types - - - - - Bitrate of encoded video in Kbps - - - - - Pixel dimensions of video - - - - - Pixel dimensions of video - - - - - Whether it is acceptable to scale the image. - - - - - Whether the ad must have its aspect ratio maintained when scales - - - - - The apiFramework defines the method to use for communication if the MediaFile is interactive. Suggested values for this element are “VPAID”, “FlashVars” (for Flash/Flex), “initParams” (for Silverlight) and “GetVariables” (variables placed in key/value pairs on the asset request). - - - - - - - - - - - - - - - - - - Any number of companions in any desired pixel dimensions. - - - - - - - - - - - - Any number of companions in any desired pixel dimensions. - - - - - - - - - - The preferred order in which multiple Creatives should be displayed - - - - - Ad-ID for the creative (formerly ISCI) - - - - - - - - - - - - - Any valid XML may be included in the Extensions node - - - - - - - - - - - Second-level element surrounding wrapper ad pointing to Secondary ad server. - - - - - - Indicates source ad server - - - - - URL of ad tag of downstream Secondary Ad Server - - - - - URL to request if ad does not play due to error - - - - - URL to request to track an impression - - - - - - - - - - - - - - - - - - URL to request for tracking purposes when user clicks on the video - - - - - - - - - - - - - - - - - - - - - Definition of Companion ad, if served separately - - - - - - - - - - - - Any number of companions in any desired pixel dimensions. - - - - - - - - - - The preferred order in which multiple Creatives should be displayed - - - - - Ad-ID for the creative (formerly ISCI) - - - - - - - - - - - - - Any valid XML may be included in the Extensions node - - - - - - - - - - - - - - - - Current version is 2.0. The 2.0.1 version corrects an error in the Wrapper section related the Linear node's TrackingEvents and VideoCLicks elements. - - - - - - - - - The name of the event to track for the Linear element. The creativeView should always be requested when present. - - - - - - - The name of the event to track. For nonlinear ads these events should be recorded on the video within the ad. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - URL to open as destination page when user clicks on the video - - - - - - - - - - - - URL to request for tracking purposes when user clicks on the video - - - - - - - - - - - - URLs to request on custom events such as hotspotted video - - - - - - - - - - - - - - - - - URL to a static file, such as an image or SWF file - - - - - - - Mime type of static resource - - - - - - - - - URL source for an IFrame to display the companion element - - - - - HTML to display the companion element - - - - - - The creativeView should always be requested when present. For Companions creativeView is the only supported event. - - - - - URL to open as destination page when user clicks on the the companion banner ad. - - - - - Alt text to be displayed when companion is rendered in HTML environment. - - - - - Data to be passed into the companion ads. The apiFramework defines the method to use for communication (e.g. “FlashVar”) - - - - - - Optional identifier - - - - - Pixel dimensions of companion - - - - - Pixel dimensions of companion - - - - - Pixel dimensions of expanding companion ad when in expanded state - - - - - Pixel dimensions of expanding companion ad when in expanded state - - - - - The apiFramework defines the method to use for communication with the companion - - - - - - - - - URL to a static file, such as an image or SWF file - - - - - - - Mime type of static resource - - - - - - - - - URL source for an IFrame to display the companion element - - - - - HTML to display the companion element - - - - - - URL to open as destination page when user clicks on the the non-linear ad unit. - - - - - Data to be passed into the video ad. - - - - - - Optional identifier - - - - - Pixel dimensions of companion - - - - - Pixel dimensions of companion - - - - - Pixel dimensions of expanding nonlinear ad when in expanded state - - - - - Pixel dimensions of expanding nonlinear ad when in expanded state - - - - - Whether it is acceptable to scale the image. - - - - - Whether the ad must have its aspect ratio maintained when scales - - - - - Suggested duration to display non-linear ad, typically for animation to complete. Expressed in standard time format hh:mm:ss - - - - - The apiFramework defines the method to use for communication with the nonlinear element - - - - - - - - - Internal version used by ad system - - - - - - - - - - - - - + attributeFormDefault="unqualified"> + + + IAB VAST, Video Ad Serving Template, video xml ad response, Version + 2.0.1, xml schema prepared by Tremor Media + + + + + + + Top-level element, wraps each ad in the response + + + + + + + Second-level element surrounding complete ad + data for a single ad + + + + + + + Indicates source ad server + + + + + + Common name of ad + + + + + + Longer description of ad + + + + + + URL of request to survey vendor + + + + + + URL to request if ad does not play + due to error + + + + + + URL to track impression + + + + + + Any number of companions in any + desired pixel dimensions. + + + + + + + Wraps each creative + element within an InLine or Wrapper + Ad + + + + + + + + + + + Duration in + standard + time format, + hh:mm:ss + + + + + + + + Data to be + passed into + the video ad + + + + + + + + + + + Location + of + linear + file + + + + + + + + + Optional + identifier + + + + + + + Method + of + delivery + of + ad + + + + + + + + + + + + + MIME + type. + Popular + MIME + types + include, + but + are + not + limited + to + “video/x-ms-wmv” + for + Windows + Media, + and + “video/x-flv” + for + Flash + Video. + Image + ads + or + interactive + ads + can + be + included + in + the + MediaFiles + section + with + appropriate + Mime + types + + + + + + + Bitrate + of + encoded + video + in + Kbps + + + + + + + Pixel + dimensions + of + video + + + + + + + Pixel + dimensions + of + video + + + + + + + Whether + it + is + acceptable + to + scale + the + image. + + + + + + + Whether + the + ad + must + have + its + aspect + ratio + maintained + when + scales + + + + + + + The + apiFramework + defines + the + method + to + use + for + communication + if + the + MediaFile + is + interactive. + Suggested + values + for + this + element + are + “VPAID”, + “FlashVars” + (for + Flash/Flex), + “initParams” + (for + Silverlight) + and + “GetVariables” + (variables + placed + in + key/value + pairs + on + the + asset + request). + + + + + + + + + + + + + + + + + + + + Any number + of + companions + in any + desired + pixel + dimensions. + + + + + + + + + + + + + + Any number + of + companions + in any + desired + pixel + dimensions. + + + + + + + + + + + The preferred + order in which multiple + Creatives should be + displayed + + + + + + Ad-ID for the + creative (formerly ISCI) + + + + + + + + + + + + + + Any valid XML may be + included in the Extensions node + + + + + + + + + + + + Second-level element surrounding wrapper ad + pointing to Secondary ad server. + + + + + + + Indicates source ad server + + + + + + URL of ad tag of downstream + Secondary Ad Server + + + + + + URL to request if ad does not play + due to error + + + + + + URL to request to track an + impression + + + + + + + + + + + + + + + + + + + + URL + to + request + for + tracking + purposes + when + user + clicks + on + the + video + + + + + + + + + + + + + + + + + + + + + + + Definition + of Companion + ad, if + served + separately + + + + + + + + + + + + + + Any number + of + companions + in any + desired + pixel + dimensions. + + + + + + + + + + + The preferred + order in which multiple + Creatives should be + displayed + + + + + + Ad-ID for the + creative (formerly ISCI) + + + + + + + + + + + + + + Any valid XML may be + included in the Extensions node + + + + + + + + + + + + + + + + + Current version is 2.0. The 2.0.1 version corrects an error in + the Wrapper section related the Linear node's TrackingEvents and VideoCLicks + elements. + + + + + + + + + + The name of the event to track for the Linear element. The + creativeView should always be requested when present. + + + + + + + + The name of the event to track. For nonlinear + ads these events should be recorded on the video within the + ad. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + URL to open as destination page when user clicks on the + video + + + + + + + + + + + + + URL to request for tracking purposes when user clicks on the + video + + + + + + + + + + + + + URLs to request on custom events such as hotspotted video + + + + + + + + + + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + + Mime type of static resource + + + + + + + + + + URL source for an IFrame to display the companion + element + + + + + + HTML to display the companion element + + + + + + The creativeView should always be requested when present. For + Companions creativeView is the only supported event. + + + + + + URL to open as destination page when user clicks on the the + companion banner ad. + + + + + + Alt text to be displayed when companion is rendered in HTML + environment. + + + + + + Data to be passed into the companion ads. The apiFramework + defines the method to use for communication (e.g. “FlashVar”) + + + + + + + Optional identifier + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of expanding companion ad when in expanded + state + + + + + + Pixel dimensions of expanding companion ad when in expanded + state + + + + + + The apiFramework defines the method to use for communication with + the companion + + + + + + + + + + URL to a static file, such as an image or SWF file + + + + + + + + Mime type of static resource + + + + + + + + + + URL source for an IFrame to display the companion + element + + + + + + HTML to display the companion element + + + + + + URL to open as destination page when user clicks on the the + non-linear ad unit. + + + + + + Data to be passed into the video ad. + + + + + + Optional identifier + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of companion + + + + + Pixel dimensions of expanding nonlinear ad when in expanded + state + + + + + + Pixel dimensions of expanding nonlinear ad when in expanded + state + + + + + + Whether it is acceptable to scale the image. + + + + + Whether the ad must have its aspect ratio maintained when scales + + + + + + Suggested duration to display non-linear ad, typically for + animation to complete. Expressed in standard time format hh:mm:ss + + + + + + The apiFramework defines the method to use for communication with + the nonlinear element + + + + + + + + + + Internal version used by ad system + + + + + + + + + + + + + \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vmap_schema.xsd b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vmap_schema.xsd new file mode 100644 index 0000000..d163144 --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/processor/assets/vmap_schema.xsd @@ -0,0 +1,184 @@ + + + + + + IAB VMAP, Video Multiple AD Playlist, Version 1.0, xml schema prepared by FreeWheel + + + + + + Top-level element, represents a single ad break, but may allow for multiple ads + + + + + + Represents the ad data that will be used to fill the ad break + + + + + + A VAST 3.0 document that comprises the ad response document. Not contained within a CDATA. (There were are a couple of places where the VMAP 1.0 pdf incorrectly references both VASTData and VASTAdData but VASTAdData is the correct element per VMAP 1.0.1) + + + + + An ad response document (included inline) that is not VAST 3.0 + + + + + URL to a secondary ad server that will provide the ad response. URL must be contained within a CDATA block + + + + + + Identifier for the ad source + + + + + Indicates whether the player should select and play only a single ad from the ad response document, or play multiple ads. If not specified, video player accepts playing multiple ads in an ad break + + + + + Whether the player should follow wrappers/redirects in the ad response document. If not specified, left to the video player’s discretion + + + + + + + Container for tracking URIs for events specific to VMAP + + + + + Container for Extensions that express additional information not supported by VMAP + + + + + + Represent the timing for the ad break. Expressed in one of four ways: (1)time format HH:MM:SS[.mmm], (2)n% (n is an integer from 0-100 and represents percentage of total duration from start to that point), (3)“start” or “end”, or (4) #m (m is an integer > 0 and represents the position of the ad break opportunity) + + + + + The type of ads allowed by the ad break: "linear", "nonlinear" or "display" (multiple types can be entered using a comma separator with no spaces). Intended to provide a "hint" to the player + + + + + Optional identifier for the ad break + + + + + Optional indicator that instructs the video player to repeat the same AdBreak and AdSource at time offsets equal to the duration value of this attribute. Expresssed in time format HH.MM.SS[.mmm] + + + + + + + + Current version is 1.0.1 + + + + + + + + + URI to track for specified event type + + + + + + + The name of the VMAP ad break level event to track + + + + + + + + + + + + + + + + + + + + The XML content of the Extension. Extension XML must use its own namespace + + + + + + + + The type of the extension. The type value must be globally unique. A URI is recommended + + + + + + + + + + + + + + + + + The ad response template employed by the ad response document + + + + + + + + + + + + + + + + + + The ad response template employed by the ad response document + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/DefaultMediaPicker.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/DefaultMediaPicker.java index 01320aa..04a0530 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/DefaultMediaPicker.java +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/DefaultMediaPicker.java @@ -44,12 +44,13 @@ */ package com.amazon.android.ads.vast.util; -import com.amazon.android.ads.vast.model.VASTMediaFile; -import com.amazon.android.ads.vast.processor.VASTMediaPicker; +import com.amazon.android.ads.vast.model.vast.MediaFile; +import com.amazon.android.ads.vast.processor.MediaPicker; import android.content.Context; import android.text.TextUtils; import android.util.DisplayMetrics; +import android.util.Log; import java.math.BigInteger; import java.util.Collections; @@ -57,7 +58,7 @@ import java.util.Iterator; import java.util.List; -public class DefaultMediaPicker implements VASTMediaPicker { +public class DefaultMediaPicker implements MediaPicker { private static final String TAG = "DefaultMediaPicker"; private static final int maxPixels = 5000; @@ -85,7 +86,7 @@ public DefaultMediaPicker(int width, int height) { @Override // given a list of MediaFiles, select the most appropriate one. - public VASTMediaFile pickVideo(List mediaFiles) { + public MediaFile pickVideo(List mediaFiles) { //make sure that the list of media files contains the correct attributes if (mediaFiles == null || prefilterMediaFiles(mediaFiles) == 0) { return null; @@ -94,30 +95,29 @@ public VASTMediaFile pickVideo(List mediaFiles) { return getBestMatch(mediaFiles); } - /* + /* * This method filters the list of mediafiles and return the count. - * Validate that the media file objects contain the required attributes for the Default Media - * Picker processing. - * - * Required attributes: - * 1. type - * 2. height - * 3. width - * 4. url - */ - - private int prefilterMediaFiles(List mediaFiles) { - - Iterator iter = mediaFiles.iterator(); + * Validate that the media file objects contain the required attributes for the Default Media + * Picker processing. + * + * Required attributes: + * 1. type + * 2. height + * 3. width + * 4. url + */ + private int prefilterMediaFiles(List mediaFiles) { + + Iterator iter = mediaFiles.iterator(); while (iter.hasNext()) { - VASTMediaFile mediaFile = iter.next(); + MediaFile mediaFile = iter.next(); // type attribute String type = mediaFile.getType(); if (TextUtils.isEmpty(type)) { - VASTLog.d(TAG, "Validator error: mediaFile type empty"); + Log.d(TAG, "Validator error: mediaFile type empty"); iter.remove(); continue; } @@ -126,16 +126,13 @@ private int prefilterMediaFiles(List mediaFiles) { BigInteger height = mediaFile.getHeight(); if (null == height) { - VASTLog - .d(TAG, "Validator error: mediaFile height null"); + Log.d(TAG, "Validator error: mediaFile height null"); iter.remove(); continue; } int videoHeight = height.intValue(); if (!(0 < videoHeight && videoHeight < maxPixels)) { - VASTLog.d(TAG, - "Validator error: mediaFile height invalid: " - + videoHeight); + Log.d(TAG, "Validator error: mediaFile height invalid: " + videoHeight); iter.remove(); continue; } @@ -143,15 +140,13 @@ private int prefilterMediaFiles(List mediaFiles) { // width attribute BigInteger width = mediaFile.getWidth(); if (null == width) { - VASTLog.d(TAG, "Validator error: mediaFile width null"); + Log.d(TAG, "Validator error: mediaFile width null"); iter.remove(); continue; } int videoWidth = width.intValue(); if (!(0 < videoWidth && videoWidth < maxPixels)) { - VASTLog.d(TAG, - "Validator error: mediaFile width invalid: " - + videoWidth); + Log.d(TAG, "Validator error: mediaFile width invalid: " + videoWidth); iter.remove(); continue; } @@ -159,7 +154,7 @@ private int prefilterMediaFiles(List mediaFiles) { // mediaFile url String url = mediaFile.getValue(); if (TextUtils.isEmpty(url)) { - VASTLog.d(TAG, "Validator error: mediaFile url empty"); + Log.d(TAG, "Validator error: mediaFile url empty"); iter.remove(); } } @@ -185,10 +180,10 @@ private void setDeviceWidthHeight(int width, int height) { } - private class AreaComparator implements Comparator { + private class AreaComparator implements Comparator { @Override - public int compare(VASTMediaFile obj1, VASTMediaFile obj2) { + public int compare(MediaFile obj1, MediaFile obj2) { // get area of the video of the two MediaFiles int obj1Area = obj1.getWidth().intValue() * obj1.getHeight().intValue(); int obj2Area = obj2.getWidth().intValue() * obj2.getHeight().intValue(); @@ -196,7 +191,7 @@ public int compare(VASTMediaFile obj1, VASTMediaFile obj2) { // get the difference between the area of the MediaFile and the area of the screen int obj1Diff = Math.abs(obj1Area - deviceArea); int obj2Diff = Math.abs(obj2Area - deviceArea); - VASTLog.v(TAG, "AreaComparator: obj1:" + obj1Diff + " obj2:" + obj2Diff); + Log.v(TAG, "AreaComparator: obj1:" + obj1Diff + " obj2:" + obj2Diff); // choose the MediaFile which has the lower difference in area if (obj1Diff < obj2Diff) { @@ -212,18 +207,18 @@ else if (obj1Diff > obj2Diff) { } - private boolean isMediaFileCompatible(VASTMediaFile media) { + private boolean isMediaFileCompatible(MediaFile media) { // check if the MediaFile is compatible with the device. // further checks can be added here return media.getType().matches(SUPPORTED_VIDEO_TYPE_REGEX); } - private VASTMediaFile getBestMatch(List list) { + private MediaFile getBestMatch(List list) { - VASTLog.d(TAG, "getBestMatch"); + Log.d(TAG, "getBestMatch"); - for (VASTMediaFile media : list) { + for (MediaFile media : list) { if (isMediaFileCompatible(media)) { return media; } diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/HttpTools.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/HttpTools.java index d96141c..654fbda 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/HttpTools.java +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/HttpTools.java @@ -45,47 +45,53 @@ package com.amazon.android.ads.vast.util; import android.text.TextUtils; +import android.util.Log; import java.net.HttpURLConnection; import java.net.URL; public class HttpTools { - private static final String TAG = HttpTools.class.getName(); + private static final String TAG = HttpTools.class.getName(); - public static void httpGetURL(final String url) { - if (!TextUtils.isEmpty(url)) { - new Thread() { - @Override - public void run() { - HttpURLConnection conn = null; - try { - VASTLog.v(TAG, "connection to URL:" + url); - URL httpUrl = new URL(url); + public static void httpGetURL(final String url) { - HttpURLConnection.setFollowRedirects(true); - conn = (HttpURLConnection) httpUrl.openConnection(); - conn.setConnectTimeout(5000); - conn.setRequestProperty("Connection", "close"); - conn.setRequestMethod("GET"); + if (!TextUtils.isEmpty(url)) { + new Thread() { + @Override + public void run() { - int code = conn.getResponseCode(); - VASTLog.v(TAG, "response code:" + code - + ", for URL:" + url); - } catch (Exception e) { - VASTLog.w(TAG, url + ": " + e.getMessage() + ":" - + e.toString()); - } finally { - if (conn != null) { - conn.disconnect(); - } - } - } - }.start(); - } else { - VASTLog.w(TAG, "url is null or empty"); - - } + HttpURLConnection conn = null; + try { + Log.d(TAG, "connection to URL:" + url); + URL httpUrl = new URL(url); - } + HttpURLConnection.setFollowRedirects(true); + conn = (HttpURLConnection) httpUrl.openConnection(); + conn.setConnectTimeout(5000); + conn.setRequestProperty("Connection", "close"); + conn.setRequestMethod("GET"); + + int code = conn.getResponseCode(); + Log.d(TAG, "response code:" + code + + ", for URL:" + url); + } + catch (Exception e) { + Log.e(TAG, url + ": " + e.getMessage() + ":" + + e.toString()); + } + finally { + if (conn != null) { + conn.disconnect(); + } + } + } + }.start(); + } + else { + Log.e(TAG, "url is null or empty"); + + } + + } } diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/NetworkTools.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/NetworkTools.java index b54601e..8f73e56 100644 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/NetworkTools.java +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/NetworkTools.java @@ -48,15 +48,18 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; +import android.util.Log; public class NetworkTools { private static final String TAG = HttpTools.class.getName(); - // This method return true if it's connected to Internet + /** + * This method returns true if it's connected to Internet + */ public static boolean connectedToInternet(Context context) { - VASTLog.d(TAG, "Testing connectivity:"); + Log.d(TAG, "Testing connectivity:"); ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); @@ -65,32 +68,33 @@ public static boolean connectedToInternet(Context context) { NetworkInfo wifiNetwork = getNetworkInfo(cm, networks, ConnectivityManager.TYPE_WIFI); if (wifiNetwork != null && wifiNetwork.isConnected()) { - VASTLog.d(TAG, "Connected to Internet"); + Log.d(TAG, "Connected to Internet"); return true; } NetworkInfo mobileNetwork = getNetworkInfo(cm, networks, ConnectivityManager.TYPE_MOBILE); if (mobileNetwork != null && mobileNetwork.isConnected()) { - VASTLog.d(TAG, "Connected to Internet"); + Log.d(TAG, "Connected to Internet"); return true; } NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); if (activeNetwork != null && activeNetwork.isConnected()) { - VASTLog.d(TAG, "Connected to Internet"); + Log.d(TAG, "Connected to Internet"); return true; } - VASTLog.d(TAG, "No Internet connection"); + Log.d(TAG, "No Internet connection"); return false; } /** * Get the NetworkInfo for the connection with the given connectionType - * @param cm ConnectivityMonitor for the context - * @param networks List of + * + * @param cm ConnectivityMonitor for the context + * @param networks List of * @param connectionType Type of network connection, e.g., ConnectivityManager.TYPE_WIFI, etc * @return The NetworkInfo for the given connection type, or null if the connection type does - * not exist + * not exist */ private static NetworkInfo getNetworkInfo(ConnectivityManager cm, Network[] networks, int connectionType) { diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VASTLog.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VASTLog.java deleted file mode 100644 index 3233deb..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VASTLog.java +++ /dev/null @@ -1,131 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.util; - -import android.util.Log; - -public class VASTLog { - - private static String TAG = "VAST"; - - public enum LOG_LEVEL { - - verbose(1), - debug(2), - info(3), - warning(4), - error(5), - none(6); - - private int value; - - LOG_LEVEL(int value) { - - this.value = value; - - } - - public int getValue() { - - return value; - } - } - - private static LOG_LEVEL LEVEL = LOG_LEVEL.verbose; - - public static void v(String tag, String msg) { - - if (LEVEL.getValue() <= LOG_LEVEL.verbose.getValue()) { - Log.v(tag, msg); - } - } - - public static void d(String tag, String msg) { - - if (LEVEL.getValue() <= LOG_LEVEL.debug.getValue()) { - Log.d(tag, msg); - } - } - - public static void i(String tag, String msg) { - - if (LEVEL.getValue() <= LOG_LEVEL.info.getValue()) { - Log.i(tag, msg); - } - } - - public static void w(String tag, String msg) { - - if (LEVEL.getValue() <= LOG_LEVEL.warning.getValue()) { - Log.w(tag, msg); - } - } - - public static void e(String tag, String msg) { - - if (LEVEL.getValue() <= LOG_LEVEL.error.getValue()) { - Log.e(tag, msg); - } - } - - public static void e(String tag, String msg, Throwable tr) { - - if (LEVEL.getValue() <= LOG_LEVEL.error.getValue()) { - Log.e(tag, msg, tr); - } - } - - public static void setLoggingLevel(LOG_LEVEL logLevel) { - - Log.i(TAG, "Changing logging level from :" + LEVEL + ". To:" + logLevel); - LEVEL = logLevel; - } - - public static LOG_LEVEL getLoggingLevel() { - - return LEVEL; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VastAdListener.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VastAdListener.java new file mode 100644 index 0000000..715e22d --- /dev/null +++ b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/VastAdListener.java @@ -0,0 +1,53 @@ +/** + * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.amazon.android.ads.vast.util; + +import com.amazon.android.ads.vast.model.vmap.AdBreak; + +import java.util.List; + +/** + * Listener to keep track of playing vast ad pods. + */ +public interface VastAdListener { + + /** + * Indicates that the VMAP response has been parsed and the ads are ready to be played. + */ + void adsReady(); + + /** + * To be called on the start of an ad pod, or chuck of ads to be played together. + * + * @param adIdx The index of the ad to start at. + * @param adList The list of ads to play. + */ + void startAdPod(int adIdx, List adList); + + /** + * To be called when the ad should start playing. + */ + void startAd(); + + /** + * To be called when the ad is done playing. + */ + void adComplete(); + + /** + * To be called when all the ads of the ad pod have been played. + */ + void adPodComplete(); +} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlTools.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlTools.java deleted file mode 100644 index ca5a804..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlTools.java +++ /dev/null @@ -1,207 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.util; - -import org.w3c.dom.CharacterData; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.io.StringWriter; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -public class XmlTools { - - private static String TAG = "XmlTools"; - - public static void logXmlDocument(Document doc) { - VASTLog.d(TAG, "logXmlDocument"); - try { - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer - .setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty( - "{http://xml.apache.org/xslt}indent-amount", "4"); - - StringWriter sw = new StringWriter(); - transformer.transform(new DOMSource(doc), new StreamResult(sw)); - - VASTLog.d(TAG, sw.toString()); - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - } - } - - public static String xmlDocumentToString(Document doc) { - String xml = null; - VASTLog.d(TAG, "xmlDocumentToString"); - try { - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer - .setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty( - "{http://xml.apache.org/xslt}indent-amount", "4"); - - StringWriter sw = new StringWriter(); - transformer.transform(new DOMSource(doc), new StreamResult(sw)); - - xml = sw.toString(); - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - } - - return xml; - } - - public static String xmlDocumentToString(Node node) { - String xml = null; - VASTLog.d(TAG, "xmlDocumentToString"); - try { - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer - .setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty( - "{http://xml.apache.org/xslt}indent-amount", "4"); - - StringWriter sw = new StringWriter(); - transformer.transform(new DOMSource(node), new StreamResult(sw)); - - xml = sw.toString(); - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - } - - return xml; - } - public static Document stringToDocument(String doc) { - VASTLog.d(TAG, "stringToDocument"); - - DocumentBuilder db; - Document document = null; - try { - db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - - InputSource is = new InputSource(); - is.setCharacterStream(new StringReader(doc)); - - document = db.parse(is); - - } catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - } - return document; - - } - - public static String stringFromStream(InputStream inputStream) - throws IOException { - VASTLog.d(TAG, "stringFromStream"); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length; - - while ((length = inputStream.read(buffer)) != -1) { - baos.write(buffer, 0, length); - } - - byte[] bytes = baos.toByteArray(); - - return new String(bytes, "UTF-8"); - } - - public static String getElementValue(Node node) { - - NodeList childNodes = node.getChildNodes(); - Node child; - String value = null; - CharacterData cd; - - for (int childIndex = 0; childIndex < childNodes.getLength(); childIndex++) { - child = childNodes.item(childIndex); - // value = child.getNodeValue().trim(); - cd = (CharacterData) child; - value = cd.getData().trim(); - - if (value.length() == 0) { - // this node was whitespace - continue; - } - - VASTLog.v(TAG, "getElementValue: " + value); - return value; - - } - return value; - } - -} diff --git a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlValidation.java b/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlValidation.java deleted file mode 100644 index 72e68cc..0000000 --- a/VastAdsComponent/src/main/java/com/amazon/android/ads/vast/util/XmlValidation.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * This file was modified by Amazon: - * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0/ - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -/* - * Copyright (c) 2014, Nexage, Inc. All rights reserved. - * Copyright (C) 2016 Amazon Inc. - * - * Provided under BSD-3 license as follows: - * - * Redistribution and use in source and binary forms, with or without modification, are permitted - * provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions - * and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions - * and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of Nexage nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR - * IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.amazon.android.ads.vast.util; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import mf.javax.xml.transform.Source; -import mf.javax.xml.transform.stream.StreamSource; -import mf.javax.xml.validation.Schema; -import mf.javax.xml.validation.SchemaFactory; -import mf.javax.xml.validation.Validator; -import mf.org.apache.xerces.jaxp.validation.XMLSchemaFactory; - -public class XmlValidation { - - private static String TAG = "XmlTools"; - - public static boolean validate(InputStream schemaStream, String xml) { - - VASTLog.i(TAG, "Beginning XSD validation."); - SchemaFactory factory = new XMLSchemaFactory(); - Source schemaSource = new StreamSource(schemaStream); - Source xmlSource = new StreamSource(new ByteArrayInputStream(xml.getBytes())); - Schema schema; - try { - schema = factory.newSchema(schemaSource); - Validator validator = schema.newValidator(); - validator.validate(xmlSource); - } - catch (Exception e) { - VASTLog.e(TAG, e.getMessage(), e); - return false; - } - VASTLog.i(TAG, "Completed XSD validation.."); - return true; - } - -} diff --git a/VastAdsComponent/src/main/res/values-en-rUS/strings.xml b/VastAdsComponent/src/main/res/values-en-rUS/strings.xml deleted file mode 120000 index c2d3f2b..0000000 --- a/VastAdsComponent/src/main/res/values-en-rUS/strings.xml +++ /dev/null @@ -1 +0,0 @@ -../values/strings.xml \ No newline at end of file diff --git a/VastAdsComponent/src/main/res/values-en-rUS/strings.xml b/VastAdsComponent/src/main/res/values-en-rUS/strings.xml new file mode 100644 index 0000000..3c92709 --- /dev/null +++ b/VastAdsComponent/src/main/res/values-en-rUS/strings.xml @@ -0,0 +1,16 @@ + + + diff --git a/VastAdsComponent/src/main/res/values/values.xml b/VastAdsComponent/src/main/res/values/values.xml index 607dd65..0688bb7 100644 --- a/VastAdsComponent/src/main/res/values/values.xml +++ b/VastAdsComponent/src/main/res/values/values.xml @@ -13,5 +13,6 @@ express or implied. See the License for the specific language governing permissions and limitations under the License. --> - "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=" + + https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator=