diff --git a/README.md b/README.md index 7362dc2a..573d09f0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ Appnexus Android SDK ================== +> [!CAUTION] +> We no longer release source code updates of Android SDK to Github. This is not a deprecation of our SDK, only an open-source deprecation as we are taking our Android and iOS Mobile SDKs private. +> Future updates starting from SDK v9.0 will be released only as compiled .aar binary file and will be published via MavenCentral or another equivalent service. +> Please contact your account managers or submit a ticket via the Customer Support Portal: https://help.xandr.com/ if you have any questions. + + See our documentation on our wiki at https://docs.xandr.com/bundle/mobile-sdk/page/xandr-mobile-sdks.html -Get the latest release notes here: https://github.com/appnexus/mobile-sdk-android/releases - -To file an issue or request an enhancement please visit the AppNexus Customer Support Portal (https://support.appnexus.com). **We do not accept GitHub issues.** +Get the latest release notes here: https://docs.xandr.com/bundle/mobile-sdk/page/android-sdk-release-notes.html diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 5a746cd1..619f4f47 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,16 @@ +## 8.11 + +### Announcements: +This is the last minor release in SDK v8.x. +The next major release will be SDK v9.0 and as already announced we will no longer release source code updates of iOS and Android Mobile SDKs to Github. This is not a deprecation of our SDKs, only an open-source deprecation as we are taking our Android and iOS Mobile SDKs private. +There will be no breaking changes from v8.x to v9.0 but keep in mind any new functional changes going forward will be developed only in version v9.x. + +### New Feature ++ 6273926 Support for Digital Services Act (DSA) [https://docs.xandr.com/bundle/mobile-sdk/page/sdk-privacy-for-android.html] + +### Improvement/Bug Fixes ++ 6273738 Fixed HTML ad that automatically open image resources in an external browser + ## 8.10 ### New Feature diff --git a/instreamvideo/build.gradle b/instreamvideo/build.gradle index a34e5088..3b92959b 100644 --- a/instreamvideo/build.gradle +++ b/instreamvideo/build.gradle @@ -1,5 +1,5 @@ // Project Properties -version = "1.49" // Instream SDK version +version = "1.50" // Instream SDK version apply plugin: 'com.android.library' @@ -10,7 +10,7 @@ android { defaultConfig { minSdkVersion 14 targetSdkVersion 32 - versionCode 48 // An integer value that represents the version of the code, relative to other versions. Increase for each release. + versionCode 49 // An integer value that represents the version of the code, relative to other versions. Increase for each release. versionName version consumerProguardFiles 'proguard-project.txt' } diff --git a/run_tests.sh b/run_tests.sh old mode 100644 new mode 100755 index 76942e25..00932850 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,20 +1,28 @@ EXIT_STATUS=0 DIR_PATH_SDK_REPORT="sdkTestReport" +DIR_PATH_SDK_REPORT_COMBINED="sdkTestReport/Combined" +DIR_PATH_SDK_REPORT_COMBINED_CLASSES="sdkTestReport/Combined/classes" +DIR_PATH_SDK_REPORT_COMBINED_PACKAGES="sdkTestReport/Combined/packages" DIR="sdk/build/reports/tests/testDebugUnitTest" DIR_CLASSES="sdk/build/reports/tests/testDebugUnitTest/classes" DIR_CLASSES_PACKAGES="sdk/build/reports/tests/testDebugUnitTest/packages" checkIfDeleteDirectory() { -if [ -d "$DIR_PATH_SDK_REPORT" ]; then rm -Rf $DIR_PATH_SDK_REPORT; -else mkdir $DIR_PATH_SDK_REPORT -fi +if [ -d "$DIR_PATH_SDK_REPORT" ]; then rm -Rf $DIR_PATH_SDK_REPORT; fi +mkdir $DIR_PATH_SDK_REPORT +mkdir $DIR_PATH_SDK_REPORT_COMBINED +mkdir $DIR_PATH_SDK_REPORT_COMBINED_CLASSES +mkdir $DIR_PATH_SDK_REPORT_COMBINED_PACKAGES } copyIndexHtml(){ -cp -r $DIR_CLASSES/* sdkTestReport/classes/ -cp -r $DIR_CLASSES_PACKAGES/* sdkTestReport/packages/ -cat $DIR/index.html >> sdkTestReport/index.html +cp -r $DIR_CLASSES/* sdkTestReport/Combined/classes/ +cp -r $DIR_CLASSES_PACKAGES/* sdkTestReport/Combined/packages/ +cat $DIR/index.html >> sdkTestReport/Combined/index.html + +mkdir $DIR_PATH_SDK_REPORT/$1 +cp -r $DIR/* sdkTestReport/$1/ } checkIfDeleteDirectory @@ -22,48 +30,48 @@ checkIfDeleteDirectory sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.AdRequestTestSuite --scan || EXIT_STATUS=$? -cp -r $DIR/* sdkTestReport || EXIT_STATUS=$? +copyIndexHtml "AdRequestTestSuite" || EXIT_STATUS=$? sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.AdResponseInfoTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "AdResponseInfoTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.AdViewTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "AdViewTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.DefaultSettingsTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "DefaultSettingsTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.MediationTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "MediationTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.MRAIDTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "MRAIDTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.NativeAdSDKTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "NativeAdSDKTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.OmidTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml +copyIndexHtml "OmidTestSuite" || EXIT_STATUS=$? sleep 6 ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.UtAdTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "UtAdTestSuite" || EXIT_STATUS=$? sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.NativeAdSDKFailedTest --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "NativeAdSDKFailedTest" || EXIT_STATUS=$? sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.NativeRequestTest --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "NativeRequestTest" || EXIT_STATUS=$? sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.MARTestSuite --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "MARTestSuite" || EXIT_STATUS=$? sleep 6 wait $! ./gradlew :sdk:testDebugUnitTest --tests com.appnexus.opensdk.suite.Miscellaneous --scan || EXIT_STATUS=$? -copyIndexHtml || EXIT_STATUS=$? +copyIndexHtml "Miscellaneous" || EXIT_STATUS=$? exit $EXIT_STATUS diff --git a/sdk/build.gradle b/sdk/build.gradle index f441c186..39d0b570 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -1,5 +1,5 @@ // Project properties -version = "8.10" +version = "8.11" group='com.appnexus.opensdk' // Android build @@ -10,7 +10,7 @@ android { buildToolsVersion '32.0.0' defaultConfig { - versionCode 107 // An integer value that represents the version of the code, relative to other versions. Increase for each release. + versionCode 108 // An integer value that represents the version of the code, relative to other versions. Increase for each release. versionName version consumerProguardFiles 'proguard-project.txt' minSdkVersion 14 diff --git a/sdk/src/com/appnexus/opensdk/ANAdResponseInfo.java b/sdk/src/com/appnexus/opensdk/ANAdResponseInfo.java index 48b6d8ed..87b73298 100644 --- a/sdk/src/com/appnexus/opensdk/ANAdResponseInfo.java +++ b/sdk/src/com/appnexus/opensdk/ANAdResponseInfo.java @@ -28,6 +28,7 @@ public class ANAdResponseInfo { private double cpm; private double cpmPublisherCurrency; private String publisherCurrencyCode = ""; + ANDSAResponseInfo dsaResponseInfo; /** @@ -154,4 +155,23 @@ public void setPublisherCurrencyCode(String publisherCurrencyCode) { this.publisherCurrencyCode = publisherCurrencyCode; } -} + /** + * Retrieves the Digital Services Act (DSA) information associated with this ad response. + * This method returns an ANDSAResponseInfo object, encapsulating details such as behalf, paid, ad render, and transparency information. + * + * @return The ANDSAResponseInfo object containing details such as behalf, paid, ad render, and transparency. + */ + public ANDSAResponseInfo getDSAResponseInfo() { + return dsaResponseInfo; + } + + /** + * Sets the Digital Services Act (DSA) information for this ad response. + * This method allows you to set an ANDSAResponseInfo object, encapsulating details such as behalf, paid, ad render, and transparency information. + * + * @param dsaResponseInfo The ANDSAResponseInfo object to be set for this ad response. + */ + public void setDSAResponseInfo(ANDSAResponseInfo dsaResponseInfo) { + this.dsaResponseInfo = dsaResponseInfo; + } +} \ No newline at end of file diff --git a/sdk/src/com/appnexus/opensdk/ANDSAResponseInfo.java b/sdk/src/com/appnexus/opensdk/ANDSAResponseInfo.java new file mode 100644 index 00000000..fce4f847 --- /dev/null +++ b/sdk/src/com/appnexus/opensdk/ANDSAResponseInfo.java @@ -0,0 +1,155 @@ +package com.appnexus.opensdk; + +import com.appnexus.opensdk.utils.JsonUtil; +import com.appnexus.opensdk.utils.StringUtil; +import org.json.JSONArray; +import org.json.JSONObject; +import java.util.ArrayList; + +/** + * Represents the response information for Digital Services Act (DSA) related data and transparency information. + * This class includes fields for information such as on whose behalf the ad is displayed, who paid for the ad, + * ad rendering information, and a list of transparency information. + * + *

Example Usage:

+ *
{@code
+ * ANDSAResponseInfo dsaResponseInfo = getAdResponseInfo().getDSAResponseInfo();
+ * String behalf = dsaResponseInfo.getBehalf();
+ * String paid = dsaResponseInfo.getPaid();
+ * ArrayList transparencyList = dsaResponseInfo.getTransparencyList();
+ * for(int i = 0; i <= transparencyList.size(); i++) {
+ *     ANDSATransparencyInfo tranparencyInfo = transparencyList.get(i);
+ *     String domain = tranparencyInfo.getDomain();
+ *     ArrayList params = tranparencyInfo.getDSAParams();
+ * }
+ *
+ * int adRender = dsaResponseInfo.getAdRender();
+ * }
+ */ +public class ANDSAResponseInfo { + private String behalf = ""; + private String paid = ""; + int adRender = -1; + private ArrayList transparencyList = new ArrayList<>(); + private static final String RESPONSE_KEY_DSA_BEHALF = "behalf"; + private static final String RESPONSE_KEY_DSA_PAID = "paid"; + private static final String RESPONSE_KEY_DSA_TRANSPARENCY = "transparency"; + private static final String RESPONSE_KEY_DSA_DOMAIN = "domain"; + private static final String RESPONSE_KEY_DSA_PARAMS = "dsaparams"; + private static final String RESPONSE_KEY_DSA_AD_RENDER = "adrender"; + + /** + * Process the dsa response from ad object + * + * @param dsaObject JsonObject that contains info of dsa + * @return ANDSAResponseInfo if no issue happened during processing + */ + public ANDSAResponseInfo create(JSONObject dsaObject) { + if (dsaObject == null) { + return null; + } + + ANDSAResponseInfo dsaResponseInfo = new ANDSAResponseInfo(); + String behalf = JsonUtil.getJSONString(dsaObject, RESPONSE_KEY_DSA_BEHALF); + if (!StringUtil.isEmpty(behalf)) { + dsaResponseInfo.setBehalf(behalf); + } + String paid = JsonUtil.getJSONString(dsaObject, RESPONSE_KEY_DSA_PAID); + if (!StringUtil.isEmpty(paid)) { + dsaResponseInfo.setPaid(paid); + } + JSONArray transparencyArray = JsonUtil.getJSONArray(dsaObject, RESPONSE_KEY_DSA_TRANSPARENCY); + if (transparencyArray != null) { + + ArrayList transparencyList = new ArrayList<>(); + for (int j = 0; j < transparencyArray.length(); j++) { + JSONObject transparencyObject = transparencyArray.optJSONObject(j); + if (transparencyObject != null) { + String domain = JsonUtil.getJSONString(transparencyObject, RESPONSE_KEY_DSA_DOMAIN); + JSONArray paramsArray = JsonUtil.getJSONArray(transparencyObject, RESPONSE_KEY_DSA_PARAMS); + + ANDSATransparencyInfo transparencyInfo = new ANDSATransparencyInfo(domain, JsonUtil.getIntegerArrayList(paramsArray)); + transparencyList.add(transparencyInfo); + } + } + dsaResponseInfo.setTransparencyList(transparencyList); + } + int adRender = JsonUtil.getJSONInt(dsaObject, RESPONSE_KEY_DSA_AD_RENDER); + if (adRender >= 0) { + dsaResponseInfo.setAdRender(adRender); + } + return dsaResponseInfo; + } + + /** + * Retrieve on whose behalf the ad is displayed. + * + * @return The behalf, returns an empty string if not set. + */ + public String getBehalf() { + return behalf; + } + + /** + * Set on whose behalf the ad is displayed. + * + * @param behalf The behalf to be set. + */ + public void setBehalf(String behalf) { + this.behalf = behalf; + } + + /** + * Retrieve who paid for the ad. + * + * @return The paid, returns an empty string if not set. + */ + public String getPaid() { + return paid; + } + + /** + * Set who paid for the ad. + * + * @param paid The paid to be set. + */ + public void setPaid(String paid) { + this.paid = paid; + } + + /** + * Retrieve indicating if the buyer/advertiser will render DSA transparency info. + * 0 = buyer/advertiser will not render + * 1 = buyer/advertiser will render + * + * @return The ad render that this ad belongs to. + */ + public int getAdRender() { + return adRender; + } + + /** + * Set indicating if the buyer/advertiser will render DSA transparency info. + * + * @param adRender The ad render value to be set. + */ + public void setAdRender(int adRender) { + this.adRender = adRender; + } + + /** + * Retrieve the transparency user parameters info + */ + public ArrayList getTransparencyList() { + return transparencyList; + } + + /** + * Set the transparency list using the provided list of ANDSATransparencyInfo. + * + * @param transparencyList The list of ANDSATransparencyInfo to be set. + */ + public void setTransparencyList(ArrayList transparencyList) { + this.transparencyList = transparencyList; + } +} \ No newline at end of file diff --git a/sdk/src/com/appnexus/opensdk/ANDSASettings.java b/sdk/src/com/appnexus/opensdk/ANDSASettings.java new file mode 100644 index 00000000..14224e83 --- /dev/null +++ b/sdk/src/com/appnexus/opensdk/ANDSASettings.java @@ -0,0 +1,114 @@ +package com.appnexus.opensdk; + +import java.util.ArrayList; + +/** + * This class encapsulates the settings related to the Digital Services Act (DSA) + * and transparency information required for ad rendering. + * + *

Example Usage:

+ *
{@code
+ * // Create an instance of ANDSASettings
+ * ANDSASettings.setDSARequired(1);
+ * ANDSASettings.setPubRender(0);
+ * // ANDSASettings.setDataToPub(1);
+ *
+ * // Create a list of transparency information
+ * ArrayList transparencyList = new ArrayList<>();
+ * transparencyList.add(new ANDSATransparencyInfo("example.com", new ArrayList<>(Arrays.asList(1, 2, 3))));
+ * transparencyList.add(new ANDSATransparencyInfo("example.net", new ArrayList<>(Arrays.asList(4, 5, 6))));
+ *
+ * // Set the transparency list in ANDSASettings
+ * ANDSASettings.setTransparencyList(transparencyList);
+ * }
+ */ +public class ANDSASettings { + private static int dsaRequired = -1; + private static int pubRender = -1; + private static int dataToPub = -1; + private static ArrayList transparencyList = new ArrayList<>(); + + /** + * Retrieve the DSA information requirement. + * + * @return The DSA information requirement value. + */ + public static int getDSARequired() { + return dsaRequired; + } + + /** + * Set the DSA information requirement. + * 0 = Not required + * 1 = Supported, bid responses with or without DSA object will be accepted + * 2 = Required, bid responses without DSA object will not be accepted + * 3 = Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * + * @param dsaRequired The DSA information requirement value to be set. + */ + public static void setDSARequired(int dsaRequired) { + ANDSASettings.dsaRequired = dsaRequired; + } + + /** + * Retrieve if the publisher renders the DSA transparency info. + * + * @return The value indicating whether the publisher renders DSA transparency info. + */ + public static int getPubRender() { + return pubRender; + } + + /** + * Set if the publisher renders the DSA transparency info. + * 0 = Publisher can't render + * 1 = Publisher could render depending on adrender + * 2 = Publisher will render + * + * @param pubRender The value indicating whether the publisher renders DSA transparency info. + */ + public static void setPubRender(int pubRender) { + ANDSASettings.pubRender = pubRender; + } + + /** + * Retrieve the transparency data if needed for audit purposes. + * + * @return The transparency data value. + */ + public static int getDataToPub() { + return dataToPub; + } + + // Publisher app should not be setting dataToPub value. This is not supported in /ut/v3 + // This is here only for Testing purpose + /** + * Set the transparency data if needed for audit purposes. + * 0 = do not send transparency data + * 1 = optional to send transparency data + * 2 = send transparency data + * + * @param dataToPub The transparency data value to be set. + */ + public static void setDataToPub(int dataToPub) { + ANDSASettings.dataToPub = dataToPub; + } + + /** + * Retrieve the transparency user parameters info. + * + * @return The list of ANDSATransparencyInfo containing transparency information. + */ + public static ArrayList getTransparencyList() { + return transparencyList; + } + + /** + * Set the transparency list using the provided list of ANDSATransparencyInfo. + * + * @param transparencyList The list of ANDSATransparencyInfo to be set. + */ + public static void setTransparencyList(ArrayList transparencyList) { + ANDSASettings.transparencyList = transparencyList; + } +} \ No newline at end of file diff --git a/sdk/src/com/appnexus/opensdk/ANDSATransparencyInfo.java b/sdk/src/com/appnexus/opensdk/ANDSATransparencyInfo.java new file mode 100644 index 00000000..ab2caa68 --- /dev/null +++ b/sdk/src/com/appnexus/opensdk/ANDSATransparencyInfo.java @@ -0,0 +1,70 @@ +package com.appnexus.opensdk; + +import java.util.ArrayList; + +/** + * Represents transparency information for Digital Services Act (DSA). + * This class encapsulates an array of objects representing entities that applied user parameters + * along with the parameters they applied. + * + *

Example usage:

+ *
+ * {@code
+ * ANDSATransparencyInfo transparencyInfo = new ANDSATransparencyInfo("example.com", new ArrayList<>(Arrays.asList(1, 2, 3)));
+ * // Retrieve transparency information
+ * String domain = transparencyInfo.getDomain();
+ * ArrayList dsaParams = transparencyInfo.getDSAParams();
+ * }
+ * 
+ */ +public class ANDSATransparencyInfo { + private String domain; + private ArrayList dsaParams; + + /** + * Constructs an ANDSATransparencyInfo instance with the specified domain and dsaParams. + * + * @param domain The domain of the entity that applied user parameters. + * @param dsaParams The list of user parameters used for the platform or sell-side. + */ + public ANDSATransparencyInfo(String domain, ArrayList dsaParams) { + this.domain = domain; + this.dsaParams = dsaParams; + } + + /** + * Retrieves the transparency user parameters, i.e., the domain of the entity that applied user parameters. + * + * @return The domain. + */ + public String getDomain() { + return domain; + } + + /** + * Sets the transparency user parameters, i.e., the domain. + * + * @param domain The domain to be set. + */ + public void setDomain(String domain) { + this.domain = domain; + } + + /** + * Retrieves the transparency user parameters, i.e., the list of user parameters used for the platform or sell-side. + * + * @return The list of user parameters. + */ + public ArrayList getDSAParams() { + return dsaParams; + } + + /** + * Sets the transparency user parameters, i.e., the list of user parameters used for the platform or sell-side. + * + * @param dsaParams The list of user parameters to be set. + */ + public void setDSAParams(ArrayList dsaParams) { + this.dsaParams = dsaParams; + } +} \ No newline at end of file diff --git a/sdk/src/com/appnexus/opensdk/AdWebView.java b/sdk/src/com/appnexus/opensdk/AdWebView.java index f28d504e..4dbff6f7 100644 --- a/sdk/src/com/appnexus/opensdk/AdWebView.java +++ b/sdk/src/com/appnexus/opensdk/AdWebView.java @@ -523,10 +523,7 @@ public void onLoadResource(WebView view, String url) { default: break; case HitTestResult.ANCHOR_TYPE: - case HitTestResult.IMAGE_ANCHOR_TYPE: case HitTestResult.SRC_ANCHOR_TYPE: - case HitTestResult.SRC_IMAGE_ANCHOR_TYPE: - if (loadURLInCorrectBrowser(url)) { fireAdClicked(); } diff --git a/sdk/src/com/appnexus/opensdk/ut/UTAdResponse.java b/sdk/src/com/appnexus/opensdk/ut/UTAdResponse.java index 06c1831b..38205d68 100644 --- a/sdk/src/com/appnexus/opensdk/ut/UTAdResponse.java +++ b/sdk/src/com/appnexus/opensdk/ut/UTAdResponse.java @@ -17,6 +17,7 @@ import android.text.TextUtils; +import com.appnexus.opensdk.ANDSAResponseInfo; import com.appnexus.opensdk.ANAdResponseInfo; import com.appnexus.opensdk.ANNativeAdResponse; import com.appnexus.opensdk.AdType; @@ -86,6 +87,7 @@ public class UTAdResponse { private static final String RESPONSE_KEY_CPM = "cpm"; private static final String RESPONSE_KEY_CPM_PUBLISHER_CURRENCY = "cpm_publisher_currency"; private static final String RESPONSE_KEY_CPM_CURRENCY_CODE = "publisher_currency_code"; + private static final String RESPONSE_KEY_DSA = "dsa"; private JSONObject tag; private Integer key = null; @@ -217,6 +219,7 @@ private void handleAdResponse(JSONObject response) throws Exception { double bidPriceCpm = JsonUtil.getJSONDouble(ad, RESPONSE_KEY_CPM); double bidPricePublisherCurrency = JsonUtil.getJSONDouble(ad, RESPONSE_KEY_CPM_PUBLISHER_CURRENCY); String bidPriceCurrencyCode = JsonUtil.getJSONString(ad, RESPONSE_KEY_CPM_CURRENCY_CODE); + JSONObject dsaObject = JsonUtil.getJSONObject(ad, RESPONSE_KEY_DSA); ANAdResponseInfo adResponseInfo = new ANAdResponseInfo(); adResponseInfo.setAdType(getAdType(adType)); @@ -228,6 +231,7 @@ private void handleAdResponse(JSONObject response) throws Exception { adResponseInfo.setCpm(bidPriceCpm); adResponseInfo.setCpmPublisherCurrency(bidPricePublisherCurrency); adResponseInfo.setPublisherCurrencyCode(bidPriceCurrencyCode); + adResponseInfo.setDSAResponseInfo(new ANDSAResponseInfo().create(dsaObject)); if (contentSource != null && contentSource.equalsIgnoreCase(UTConstants.CSM)) { diff --git a/sdk/src/com/appnexus/opensdk/ut/UTRequestParameters.java b/sdk/src/com/appnexus/opensdk/ut/UTRequestParameters.java index 4457def5..33b5a58a 100644 --- a/sdk/src/com/appnexus/opensdk/ut/UTRequestParameters.java +++ b/sdk/src/com/appnexus/opensdk/ut/UTRequestParameters.java @@ -28,6 +28,8 @@ import android.util.Pair; import com.appnexus.opensdk.ANClickThroughAction; +import com.appnexus.opensdk.ANDSASettings; +import com.appnexus.opensdk.ANDSATransparencyInfo; import com.appnexus.opensdk.ANGDPRSettings; import com.appnexus.opensdk.ANMultiAdRequest; import com.appnexus.opensdk.ANUSPrivacySettings; @@ -52,6 +54,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.UUID; import com.appnexus.opensdk.viewability.ANOmidViewabilty; @@ -181,6 +184,15 @@ public class UTRequestParameters { private static final String REQUEST_CONTENT = "request_content"; private static final String CONTENT_LANGUAGE = "language"; + // DSA + private static final String DSA = "dsa"; + private static final String DSA_REQUIRED = "dsarequired"; + private static final String DSA_PUB_RENDER = "pubrender"; + private static final String DSA_DATA_TO_PUB = "datatopub"; + private static final String DSA_TRANSPARENCY = "transparency"; + private static final String DSA_DOMAIN = "domain"; + private static final String DSA_PARAMS = "dsaparams"; + private static final int ALLOWED_TYPE_BANNER = 1; private static final int ALLOWED_TYPE_INTERSTITIAL = 3; private static final int ALLOWED_TYPE_VIDEO = 4; @@ -583,6 +595,12 @@ String getPostData() { postData.put(PRIVACY, privacy); } + // add DSA Privacy + JSONObject dsaPrivacy = getDSAPrivacyObject(); + if (dsaPrivacy != null && dsaPrivacy.length() > 0) { + postData.put(DSA, dsaPrivacy); + } + // add IAB Support Signal if (SDKSettings.getOMEnabled()) { @@ -608,6 +626,42 @@ String getPostData() { return postData.toString(); } + private JSONObject getDSAPrivacyObject() { + JSONObject dsaPrivacyObject = new JSONObject(); + try { + if (ANDSASettings.getDSARequired() > -1) { + dsaPrivacyObject.put(DSA_REQUIRED, ANDSASettings.getDSARequired()); + } + if (ANDSASettings.getPubRender() > -1) { + dsaPrivacyObject.put(DSA_PUB_RENDER, ANDSASettings.getPubRender()); + } + if (ANDSASettings.getDataToPub() > -1) { + dsaPrivacyObject.put(DSA_DATA_TO_PUB, ANDSASettings.getDataToPub()); + } + + ArrayList transparencyList = ANDSASettings.getTransparencyList(); + if (transparencyList != null && !transparencyList.isEmpty()) { + JSONArray transparencyArray = new JSONArray(); + for (ANDSATransparencyInfo transparency : transparencyList) { + String domain = transparency.getDomain(); + if (domain != null && !domain.isEmpty()) { + JSONObject transparencyObject = new JSONObject(); + transparencyObject.put(DSA_DOMAIN, domain); + + List params = transparency.getDSAParams(); + if (params != null && !params.isEmpty()) { + transparencyObject.put(DSA_PARAMS, new JSONArray(params)); + } + transparencyArray.put(transparencyObject); + } + } + dsaPrivacyObject.put(DSA_TRANSPARENCY, transparencyArray); + } + }catch (JSONException e) { + } + return dsaPrivacyObject; + } + private JSONObject getPrivacyObject() { JSONObject privacyObject = new JSONObject(); try { diff --git a/sdk/src/com/appnexus/opensdk/utils/JsonUtil.java b/sdk/src/com/appnexus/opensdk/utils/JsonUtil.java index b6b4c1a4..23c0ffde 100644 --- a/sdk/src/com/appnexus/opensdk/utils/JsonUtil.java +++ b/sdk/src/com/appnexus/opensdk/utils/JsonUtil.java @@ -35,6 +35,23 @@ public static ArrayList getStringArrayList(JSONArray array) { return null; } + public static ArrayList getIntegerArrayList(JSONArray array) { + if (array == null) return null; + try { + if (array.length() > 0) { + int l = array.length(); + ArrayList arrayList = new ArrayList<>(l); + for (int i = 0; i < l; i++) { + arrayList.add(array.getInt(i)); + } + return arrayList; + } + } catch (JSONException ignored) { + } catch (ClassCastException ignored) { + } + return null; + } + public static HashMap getStringObjectHashMap(JSONObject object) { if (object == null) return null; try { diff --git a/sdk/src/com/appnexus/opensdk/utils/Settings.java b/sdk/src/com/appnexus/opensdk/utils/Settings.java index 2847ab51..9905df10 100644 --- a/sdk/src/com/appnexus/opensdk/utils/Settings.java +++ b/sdk/src/com/appnexus/opensdk/utils/Settings.java @@ -68,7 +68,7 @@ public enum ImpressionType { public boolean debug_mode = false; // This should always be false here. public String ua = null; - public final String sdkVersion = "8.10"; + public final String sdkVersion = "8.11"; // BuildConfig.VERSION_NAME; diff --git a/sdk/test/com/appnexus/opensdk/TestResponsesUT.java b/sdk/test/com/appnexus/opensdk/TestResponsesUT.java index fc1e33d7..78605274 100644 --- a/sdk/test/com/appnexus/opensdk/TestResponsesUT.java +++ b/sdk/test/com/appnexus/opensdk/TestResponsesUT.java @@ -101,6 +101,7 @@ public static void setTestURL(String url) { public static final String RTB_VIDEO = "{\"cpm\":0.000010,\"cpm_publisher_currency\":0.000010,\"publisher_currency_code\":\"$\",\"content_source\":\"rtb\",\"ad_type\":\"video\",\"notify_url\":\"%s\",\"buyer_member_id\":123,\"creative_id\":6332753,\"media_type_id\":4,\"media_subtype_id\":64,\"client_initiated_ad_counting\":true,\"rtb\":{\"video\":{\"content\":\"%s\",\"duration_ms\":100}}}"; public static final String CSR_NATIVE = "{\"cpm\":0.000010,\"cpm_publisher_currency\":0.000010,\"publisher_currency_code\":\"$\",\"version\":\"3.0.0\",\"tags\":[{\"tag_id\":16268678,\"auction_id\":\"4050477843877235823\",\"nobid\":false,\"no_ad_url\":\"https://nym1-mobile.adnxs.com/it\",\"timeout_ms\":0,\"ad_profile_id\":1266762,\"rtb_video_fallback\":false,\"ads\":[{\"content_source\":\"csr\",\"ad_type\":\"native\",\"buyer_member_id\":10094,\"creative_id\":163940558,\"media_type_id\":12,\"media_subtype_id\":65,\"brand_category_id\":17,\"client_initiated_ad_counting\":true,\"viewability\":{\"config\":\"\"},\"csr\":{\"timeout_ms\":500,\"handler\":[{\"type\":\"android\",\"class\":\"%s\",\"payload\":\"{\\\"placement_id\\\":\\\"333673923704415_469697383435401\\\"}\",\"id\":\"333673923704415_469697383435401\"},{\"type\":\"ios\",\"class\":\"ANAdAdapterCSRNativeBannerFacebook\",\"payload\":\"test param\",\"id\":\"333673923704415_469697383435401\"}],\"trackers\":[{\"impression_urls\":[\"https://nym1-mobile.adnxs.com/it\"],\"video_events\":{}}],\"request_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_req\",\"response_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_resp\"}}]}]}"; public static final String MULTIPLE_IMPRESSION_URLS = "{\"\"}"; + public static final String BANNER_DSA_CONTENT_TEST = "{\"dsa\":{\"behalf\":\"test\",\"paid\":\"testname\",\"transparency\":[{\"domain\":\"test.com\",\"dsaparams\":[1,2,3]}],\"adrender\": 1},\"content_source\":\"rtb\",\"ad_type\":\"banner\",\"buyer_member_id\":123,\"creative_id\":4332753,\"media_type_id\":1,\"media_subtype_id\":1,\"client_initiated_ad_counting\":true,\"rtb\":{\"banner\":{\"content\":\"%s\",\"width\":%d,\"height\":%d},\"trackers\":[{\"impression_urls\":[\"%s\"],\"video_events\":{}}]}}"; public static String mediationNoFillThenCSRSuccessfull() { return "{\"version\":\"3.0.0\",\"tags\":[{\"tag_id\":16268678,\"auction_id\":\"4050477843877235823\",\"nobid\":false,\"no_ad_url\":\"https://nym1-mobile.adnxs.com/it\",\"timeout_ms\":0,\"ad_profile_id\":1266762,\"rtb_video_fallback\":false,\"ads\":[{\"content_source\":\"csm\",\"ad_type\":\"native\",\"buyer_member_id\":10094,\"creative_id\":163940558,\"media_type_id\":12,\"media_subtype_id\":65,\"brand_category_id\":17,\"client_initiated_ad_counting\":true,\"viewability\":{\"config\":\"\"},\"csm\":{\"timeout_ms\":500,\"handler\":[{\"type\":\"android\",\"class\":\"com.appnexus.opensdk.testviews.MediatedNativeNoFill\",\"param\":\"test param\",\"id\":\"2038077109846299_2317914228529251\"},{\"type\":\"ios\",\"class\":\"ANAdAdapterNativeFacebook\",\"param\":\"test param\",\"id\":\"2038077109846299_2317914228529251\"}],\"trackers\":[{\"impression_urls\":[\"https://nym1-mobile.adnxs.com/it\"],\"video_events\":{}}],\"request_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_req\",\"response_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_resp\"}},{\"content_source\":\"csr\",\"ad_type\":\"native\",\"buyer_member_id\":10094,\"creative_id\":163940558,\"media_type_id\":12,\"media_subtype_id\":65,\"brand_category_id\":17,\"client_initiated_ad_counting\":true,\"viewability\":{\"config\":\"\"},\"csr\":{\"timeout_ms\":500,\"handler\":[{\"type\":\"android\",\"class\":\"com.appnexus.opensdk.testviews.CSRNativeSuccessful\",\"payload\":\"{\\\"placement_id\\\":\\\"333673923704415_469697383435401\\\"}\",\"id\":\"333673923704415_469697383435401\"},{\"type\":\"ios\",\"class\":\"ANAdAdapterCSRNativeBannerFacebook\",\"payload\":\"test param\",\"id\":\"333673923704415_469697383435401\"}],\"trackers\":[{\"impression_urls\":[\"https://nym1-mobile.adnxs.com/it\"],\"video_events\":{}}],\"request_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_req\",\"response_url\":\"https://nym1-mobile.adnxs.com/mediation/v2/log_resp\"}}]}]}"; @@ -514,6 +515,10 @@ ICON_URL, templateNativeMainMedia(IMAGE_URL, 300, 200, "http://path_to_main2.com return templateResponse(NO_BID_FALSE, NO_AD_URL, ads); } + public static String bannerWithDSAResponse() { + String dsaContent = String.format(DUMMY_BANNER_CONTENT, "Test Banner Content"); + return templateBannerWithDSAAdResponse(dsaContent, 320, 50, IMPRESSION_URL); + } // templates @@ -692,6 +697,15 @@ private static String templateNativeRating(float value, float scale) { return String.format(NATIVE_RATING, value, scale); } + private static String templateBannerWithDSAAdResponse(String content, int width, int height, String impressionURL) { + String bannerDsaAd = bannerDSAAdResponse(content, width, height, impressionURL); + String ads = String.format(ADS, bannerDsaAd); + return templateResponse(NO_BID_FALSE, NO_AD_URL, ads); + } + + private static String bannerDSAAdResponse(String content, int width, int height, String impressionURL) { + return (String.format(BANNER_DSA_CONTENT_TEST, content, width, height, impressionURL)); + } private static final String DUMMY_VIDEO_CONTENT = "\n" + "\n" + diff --git a/sdk/test/com/appnexus/opensdk/UTAdRequestTest.java b/sdk/test/com/appnexus/opensdk/UTAdRequestTest.java index 2b3e5bb6..2dfc9dc8 100644 --- a/sdk/test/com/appnexus/opensdk/UTAdRequestTest.java +++ b/sdk/test/com/appnexus/opensdk/UTAdRequestTest.java @@ -30,6 +30,7 @@ import org.robolectric.shadows.ShadowLog; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -1143,6 +1144,44 @@ public void testContentLanguageParams() throws Exception { assertTrue(geoOverride.getString(CONTENT_LANGUAGE).equals("EN")); } + /** + * Test DSA parameters in /ut request body + * + * @throws Exception + */ + @Test + public void testDSAParams() throws Exception { + + // Default params + executionSteps(); + JSONObject postDataBefore = inspectPostData(); + assertFalse(postDataBefore.has("dsa")); + + // Set the params + ANDSASettings.setDSARequired(1); + ANDSASettings.setPubRender(0); + ANDSASettings.setDataToPub(1); + ArrayList transparencyList = new ArrayList<>(); + transparencyList.add(new ANDSATransparencyInfo("example.com", new ArrayList<>(Arrays.asList(1, 2, 3)))); + ANDSASettings.setTransparencyList(transparencyList); + executionSteps(); + JSONObject postData = inspectPostData(); + assertTrue(postData.has("dsa")); + JSONObject dsaObject = postData.getJSONObject("dsa"); + assertEquals(1, dsaObject.getInt("dsarequired")); + assertEquals(0, dsaObject.getInt("pubrender")); + assertEquals(1, dsaObject.getInt("datatopub")); + JSONArray transparencyArray = dsaObject.getJSONArray("transparency"); + ArrayList expectedList = new ArrayList<>(); + for (ANDSATransparencyInfo transparencyInfo : transparencyList) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("domain", transparencyInfo.getDomain()); + jsonObject.put("dsaparams", new JSONArray(transparencyInfo.getDSAParams())); + expectedList.add(jsonObject.toString()); + } + assertEquals(expectedList.toString(), transparencyArray.toString()); + } + @Override public void tearDown() { super.tearDown(); diff --git a/sdk/test/com/appnexus/opensdk/UTAdResponseTest.java b/sdk/test/com/appnexus/opensdk/UTAdResponseTest.java index a6719e7e..bb1a16cf 100644 --- a/sdk/test/com/appnexus/opensdk/UTAdResponseTest.java +++ b/sdk/test/com/appnexus/opensdk/UTAdResponseTest.java @@ -9,13 +9,14 @@ import com.appnexus.opensdk.ut.adresponse.RTBNativeAdResponse; import com.appnexus.opensdk.ut.adresponse.RTBVASTAdResponse; import com.appnexus.opensdk.ut.adresponse.SSMHTMLAdResponse; - import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; @@ -241,6 +242,29 @@ public void testBannerSSMNoURLResponse() throws Exception { @SuppressWarnings("UnusedAssignment") LinkedList list = utAdResponse.getAdList(); } + /** + * Tests banner DSA response + * + * @throws Exception + */ + @Test + public void testBannerDSAResponse() throws Exception { + String bannerWithDSAAdResponse = TestResponsesUT.bannerWithDSAResponse(); + utAdResponse = new UTAdResponse(bannerWithDSAAdResponse, null, MediaType.BANNER, "v"); + + assertNotNull(utAdResponse); + LinkedList list = utAdResponse.getAdList(); + assertNotNull(utAdResponse.getAdList()); + while (!list.isEmpty()) { + BaseAdResponse baseAdResponse = (BaseAdResponse) list.removeFirst(); + assertEquals("test", baseAdResponse.getAdResponseInfo().getDSAResponseInfo().getBehalf()); + assertEquals("testname", baseAdResponse.getAdResponseInfo().getDSAResponseInfo().getPaid()); + assertEquals(1, baseAdResponse.getAdResponseInfo().getDSAResponseInfo().getAdRender()); + ANDSATransparencyInfo expectedTransparency = baseAdResponse.getAdResponseInfo().getDSAResponseInfo().getTransparencyList().get(0); + assertEquals("test.com", expectedTransparency.getDomain()); + assertEquals(Arrays.asList(1, 2, 3), expectedTransparency.getDSAParams()); + } + } @Override public void tearDown() {