From 9501fbb691ea98d6b2cf283f1b4dec3cb5e240c9 Mon Sep 17 00:00:00 2001 From: bichenwang Date: Thu, 1 Aug 2024 15:39:24 -0700 Subject: [PATCH] 5.0.0 Mediation adapter (#81) * [HB-6645] Helium rebranding * [HB-6442] Refactor show to take Activity instead of Context * AdapterConfiguration * Remove unnecessary empty constructor * [HB-7794] New ChartboostMediation error design (#68) * New ChartboostMediation error design * [HB-7794] Remove test exception throwing * Update CLA links (#69) * [HB-7637] Partner Adapter Rework (#70) * Gradle 8 migration (#71) * Gradle 8 migration * JDK 17 instead of 11 in build files * Revert "JDK 17 instead of 11 in build files" This reverts commit 5526b596b7fac24393d8a0c05fb8eae76eb8750a. * Explicitely define JDK 17 in build.gradle.kts * Revert "Explicitely define JDK 17 in build.gradle.kts" This reverts commit e0dbc0e5a2a416b2d18852db482e0ca32d00aaf7. * Pre 5.0 getting ready for release (#72) * [HB-7689] Version pumps for Mediation 5 (#73) * API 34 (#74) * Initialization of `MobileAds` SDK on the `IO` context for mediation 5 adapter (#76) * Initialization of `MobileAds` SDK on the `IO` context for mediation 5 adapter. * Removed the unneeded `return@withContext` * HB-8087 Privacy Audit (#77) * Add correct banner size return for 5.0 * Banner Size Update (#80) * fixing build * jdk 17 * java 17 * bad rebase --------- Co-authored-by: vu Co-authored-by: Ray Graham Co-authored-by: CB-RyanMcCormick <122290337+CB-RyanMcCormick@users.noreply.github.com> --- .github/workflows/ci-tests.yml | 4 +- .github/workflows/prerelease.yml | 4 +- .github/workflows/release.yml | 4 +- AdMobAdapter/build.gradle.kts | 33 +- .../mediation/admobadapter/AdMobAdapter.kt | 445 ++++++++---------- .../admobadapter/AdMobAdapterConfiguration.kt | 67 +++ CHANGELOG.md | 4 + CONTRIBUTING.md | 4 +- README.md | 6 +- build.gradle.kts | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 4 +- 12 files changed, 316 insertions(+), 269 deletions(-) create mode 100644 AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapterConfiguration.kt diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index ed03bee..360e302 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -16,10 +16,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 79c43e8..2a85375 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fce2f80..c0f9d6b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' cache: gradle diff --git a/AdMobAdapter/build.gradle.kts b/AdMobAdapter/build.gradle.kts index 8ffe1cd..76f8a1e 100644 --- a/AdMobAdapter/build.gradle.kts +++ b/AdMobAdapter/build.gradle.kts @@ -1,6 +1,6 @@ /* * Copyright 2022-2024 Chartboost, Inc. - * + * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE file. */ @@ -17,24 +17,31 @@ plugins { repositories { google() mavenCentral() + maven("https://cboost.jfrog.io/artifactory/private-chartboost-core/") { + credentials { + username = System.getenv("JFROG_USER") + password = System.getenv("JFROG_PASS") + } + } maven("https://cboost.jfrog.io/artifactory/private-chartboost-mediation/") { credentials { username = System.getenv("JFROG_USER") password = System.getenv("JFROG_PASS") } } + maven("https://cboost.jfrog.io/artifactory/chartboost-core/") maven("https://cboost.jfrog.io/artifactory/chartboost-mediation/") } android { namespace = "com.chartboost.mediation.admobadapter" - compileSdk = 33 + compileSdk = 34 defaultConfig { minSdk = 21 - targetSdk = 33 + targetSdk = 34 // If you touch the following line, don't forget to update scripts/get_rc_version.zsh - android.defaultConfig.versionName = System.getenv("VERSION_OVERRIDE") ?: "4.22.3.0.6" + android.defaultConfig.versionName = System.getenv("VERSION_OVERRIDE") ?: "5.23.1.0.0" buildConfigField( "String", @@ -49,6 +56,7 @@ android { productFlavors { create("local") create("remote") + create("candidate") } buildTypes { @@ -63,18 +71,29 @@ android { buildFeatures { viewBinding = true + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" } } dependencies { - "localImplementation"(project(":Helium")) + "localImplementation"(project(":ChartboostMediation")) // For external usage, please use the following production dependency. // You may choose a different release version. - "remoteImplementation"("com.chartboost:chartboost-mediation-sdk:4.0.0") + "remoteImplementation"("com.chartboost:chartboost-mediation-sdk:5.0.0") + "candidateImplementation"("com.chartboost:chartboost-mediation-sdk:5.0.0") // Partner SDK - implementation("com.google.android.gms:play-services-ads:22.3.0") + implementation("com.google.android.gms:play-services-ads:23.1.0") // Adapter Dependencies implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") diff --git a/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapter.kt b/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapter.kt index e6a0423..6f4854b 100644 --- a/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapter.kt +++ b/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapter.kt @@ -1,6 +1,6 @@ /* * Copyright 2023-2024 Chartboost, Inc. - * + * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE file. */ @@ -13,13 +13,45 @@ import android.os.Bundle import android.util.Size import android.view.View.GONE import android.view.View.VISIBLE -import com.chartboost.heliumsdk.domain.* -import com.chartboost.heliumsdk.domain.AdFormat -import com.chartboost.heliumsdk.utils.PartnerLogController -import com.chartboost.heliumsdk.utils.PartnerLogController.PartnerAdapterEvents.* +import com.chartboost.chartboostmediationsdk.ad.ChartboostMediationBannerAdView.ChartboostMediationBannerSize.Companion.asSize +import com.chartboost.chartboostmediationsdk.domain.* +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.BIDDER_INFO_FETCH_STARTED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.BIDDER_INFO_FETCH_SUCCEEDED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.CUSTOM +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.DID_CLICK +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.DID_DISMISS +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.DID_REWARD +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.DID_TRACK_IMPRESSION +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.INVALIDATE_FAILED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.INVALIDATE_STARTED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.INVALIDATE_SUCCEEDED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.LOAD_FAILED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.LOAD_STARTED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.LOAD_SUCCEEDED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SETUP_FAILED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SETUP_STARTED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SETUP_SUCCEEDED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SHOW_FAILED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SHOW_STARTED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.SHOW_SUCCEEDED +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.USER_IS_NOT_UNDERAGE +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController.PartnerAdapterEvents.USER_IS_UNDERAGE +import com.chartboost.core.consent.ConsentKey +import com.chartboost.core.consent.ConsentKeys +import com.chartboost.core.consent.ConsentValue +import com.chartboost.core.consent.ConsentValues import com.chartboost.mediation.admobadapter.AdMobAdapter.Companion.getChartboostMediationError import com.google.ads.mediation.admob.AdMobAdapter -import com.google.android.gms.ads.* +import com.google.android.gms.ads.AdError +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdSize +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.FullScreenContentCallback +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.RequestConfiguration import com.google.android.gms.ads.initialization.AdapterStatus import com.google.android.gms.ads.interstitial.InterstitialAd import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback @@ -39,33 +71,6 @@ import kotlin.coroutines.resume class AdMobAdapter : PartnerAdapter { companion object { - /** - * List containing device IDs to be set for enabling AdMob test ads. It can be populated at - * any time and will take effect for the next ad request. Remember to empty this list or - * stop setting it before releasing your app. - */ - public var testDeviceIds = listOf() - set(value) { - field = value - PartnerLogController.log( - CUSTOM, - "AdMob test device ID(s) to be set: ${ - if (value.isEmpty()) { - "none" - } else { - value.joinToString() - } - }", - ) - - // There have been known ANRs when calling setRequestConfiguration() on the main thread. - CoroutineScope(IO).launch { - MobileAds.setRequestConfiguration( - RequestConfiguration.Builder().setTestDeviceIds(value).build(), - ) - } - } - /** * Convert a given AdMob error code into a [ChartboostMediationError]. * @@ -75,13 +80,13 @@ class AdMobAdapter : PartnerAdapter { */ internal fun getChartboostMediationError(error: Int) = when (error) { - AdRequest.ERROR_CODE_APP_ID_MISSING -> ChartboostMediationError.CM_LOAD_FAILURE_PARTNER_NOT_INITIALIZED - AdRequest.ERROR_CODE_INTERNAL_ERROR -> ChartboostMediationError.CM_INTERNAL_ERROR - AdRequest.ERROR_CODE_INVALID_AD_STRING -> ChartboostMediationError.CM_LOAD_FAILURE_INVALID_AD_MARKUP - AdRequest.ERROR_CODE_INVALID_REQUEST, AdRequest.ERROR_CODE_REQUEST_ID_MISMATCH -> ChartboostMediationError.CM_LOAD_FAILURE_INVALID_AD_REQUEST - AdRequest.ERROR_CODE_NETWORK_ERROR -> ChartboostMediationError.CM_NO_CONNECTIVITY - AdRequest.ERROR_CODE_NO_FILL -> ChartboostMediationError.CM_LOAD_FAILURE_NO_FILL - else -> ChartboostMediationError.CM_PARTNER_ERROR + AdRequest.ERROR_CODE_APP_ID_MISSING -> ChartboostMediationError.LoadError.PartnerNotInitialized + AdRequest.ERROR_CODE_INTERNAL_ERROR -> ChartboostMediationError.OtherError.InternalError + AdRequest.ERROR_CODE_INVALID_AD_STRING -> ChartboostMediationError.LoadError.InvalidAdMarkup + AdRequest.ERROR_CODE_INVALID_REQUEST, AdRequest.ERROR_CODE_REQUEST_ID_MISMATCH -> ChartboostMediationError.LoadError.InvalidAdRequest + AdRequest.ERROR_CODE_NETWORK_ERROR -> ChartboostMediationError.OtherError.NoConnectivity + AdRequest.ERROR_CODE_NO_FILL -> ChartboostMediationError.LoadError.NoFill + else -> ChartboostMediationError.OtherError.PartnerError } /** @@ -91,19 +96,19 @@ class AdMobAdapter : PartnerAdapter { } /** - * A map of Chartboost Mediation's listeners for the corresponding load identifier. + * The AdMob adapter configuration. */ - private val listeners = mutableMapOf() + override var configuration: PartnerAdapterConfiguration = AdMobAdapterConfiguration /** - * Indicate whether GDPR currently applies to the user. + * A map of Chartboost Mediation's listeners for the corresponding load identifier. */ - private var gdprApplies: Boolean? = null + private val listeners = mutableMapOf() /** - * Indicate whether the user has consented to allowing personalized ads when GDPR applies. + * Indicates whether the user has consented to allowing personalized ads when GDPR applies. */ - private var allowPersonalizedAds = false + private var allowPersonalizedAds = true /** * Indicate whether the user has given consent per CCPA. @@ -111,39 +116,9 @@ class AdMobAdapter : PartnerAdapter { private var ccpaPrivacyString: String? = null /** - * Get the Google Mobile Ads SDK version. - * - * Note that the version string will be in the format of afma-sdk-a-v221908999.214106000.1. - */ - override val partnerSdkVersion: String - get() = MobileAds.getVersion().toString() - - /** - * Get the AdMob adapter version. - * - * You may version the adapter using any preferred convention, but it is recommended to apply the - * following format if the adapter will be published by Chartboost Mediation: - * - * Chartboost Mediation.Partner.Adapter - * - * "Chartboost Mediation" represents the Chartboost Mediation SDK’s major version that is compatible with this adapter. This must be 1 digit. - * "Partner" represents the partner SDK’s major.minor.patch.x (where x is optional) version that is compatible with this adapter. This can be 3-4 digits. - * "Adapter" represents this adapter’s version (starting with 0), which resets to 0 when the partner SDK’s version changes. This must be 1 digit. + * Indicates whether the user has restricted their data for advertising use. */ - override val adapterVersion: String - get() = BuildConfig.CHARTBOOST_MEDIATION_ADMOB_ADAPTER_VERSION - - /** - * Get the partner name for internal uses. - */ - override val partnerId: String - get() = "admob" - - /** - * Get the partner name for external uses. - */ - override val partnerDisplayName: String - get() = "AdMob" + private var restrictedDataProcessingEnabled = false /** * Initialize the Google Mobile Ads SDK so that it is ready to request ads. @@ -154,17 +129,17 @@ class AdMobAdapter : PartnerAdapter { override suspend fun setUp( context: Context, partnerConfiguration: PartnerConfiguration, - ): Result = withContext(IO) { + ): Result> = withContext(IO) { PartnerLogController.log(SETUP_STARTED) - // Since Chartboost Mediation is the mediator, no need to initialize AdMob's partner SDKs. - // https://developers.google.com/android/reference/com/google/android/gms/ads/MobileAds?hl=en#disableMediationAdapterInitialization(android.content.Context) - // - // There have been known ANRs when calling disableMediationAdapterInitialization() on the main thread. - MobileAds.disableMediationAdapterInitialization(context) + // Since Chartboost Mediation is the mediator, no need to initialize AdMob's partner SDKs. + // https://developers.google.com/android/reference/com/google/android/gms/ads/MobileAds?hl=en#disableMediationAdapterInitialization(android.content.Context) + // + // There have been known ANRs when calling disableMediationAdapterInitialization() on the main thread. + MobileAds.disableMediationAdapterInitialization(context) - return@withContext suspendCancellableCoroutine { continuation -> - fun resumeOnce(result: Result) { + suspendCancellableCoroutine { continuation -> + fun resumeOnce(result: Result>) { if (continuation.isActive) { continuation.resume(result) } @@ -176,79 +151,21 @@ class AdMobAdapter : PartnerAdapter { } } - /** - * Notify the Google Mobile Ads SDK of the GDPR applicability and consent status. - * - * @param context The current [Context]. - * @param applies True if GDPR applies, false otherwise. - * @param gdprConsentStatus The user's GDPR consent status. - */ - override fun setGdpr( - context: Context, - applies: Boolean?, - gdprConsentStatus: GdprConsentStatus, - ) { - PartnerLogController.log( - when (applies) { - true -> GDPR_APPLICABLE - false -> GDPR_NOT_APPLICABLE - else -> GDPR_UNKNOWN - }, - ) - - PartnerLogController.log( - when (gdprConsentStatus) { - GdprConsentStatus.GDPR_CONSENT_UNKNOWN -> GDPR_CONSENT_UNKNOWN - GdprConsentStatus.GDPR_CONSENT_GRANTED -> GDPR_CONSENT_GRANTED - GdprConsentStatus.GDPR_CONSENT_DENIED -> GDPR_CONSENT_DENIED - }, - ) - - this.gdprApplies = applies - - if (applies == true) { - allowPersonalizedAds = gdprConsentStatus == GdprConsentStatus.GDPR_CONSENT_GRANTED - } - } - - /** - * Save the current CCPA privacy String to be used later. - * - * @param context The current [Context]. - * @param hasGrantedCcpaConsent True if the user has granted CCPA consent, false otherwise. - * @param privacyString The CCPA privacy String. - */ - override fun setCcpaConsent( - context: Context, - hasGrantedCcpaConsent: Boolean, - privacyString: String, - ) { - PartnerLogController.log( - if (hasGrantedCcpaConsent) { - CCPA_CONSENT_GRANTED - } else { - CCPA_CONSENT_DENIED - }, - ) - - ccpaPrivacyString = privacyString - } - /** * Notify AdMob of the COPPA subjectivity. * * @param context The current [Context]. - * @param isSubjectToCoppa True if the user is subject to COPPA, false otherwise. + * @param isUserUnderage True if the user is subject to COPPA, false otherwise. */ - override fun setUserSubjectToCoppa( + override fun setIsUserUnderage( context: Context, - isSubjectToCoppa: Boolean, + isUserUnderage: Boolean, ) { PartnerLogController.log( - if (isSubjectToCoppa) { - COPPA_SUBJECT + if (isUserUnderage) { + USER_IS_UNDERAGE } else { - COPPA_NOT_SUBJECT + USER_IS_NOT_UNDERAGE }, ) @@ -257,7 +174,7 @@ class AdMobAdapter : PartnerAdapter { MobileAds.setRequestConfiguration( MobileAds.getRequestConfiguration().toBuilder() .setTagForChildDirectedTreatment( - if (isSubjectToCoppa) { + if (isUserUnderage) { RequestConfiguration.TAG_FOR_CHILD_DIRECTED_TREATMENT_TRUE } else { RequestConfiguration.TAG_FOR_CHILD_DIRECTED_TREATMENT_FALSE @@ -271,17 +188,17 @@ class AdMobAdapter : PartnerAdapter { * Get a bid token if network bidding is supported. * * @param context The current [Context]. - * @param request The [PreBidRequest] instance containing relevant data for the current bid request. + * @param request The [PartnerAdPreBidRequest] instance containing relevant data for the current bid request. * * @return A Map of biddable token Strings. */ override suspend fun fetchBidderInformation( context: Context, - request: PreBidRequest, - ): Map { + request: PartnerAdPreBidRequest, + ): Result> { PartnerLogController.log(BIDDER_INFO_FETCH_STARTED) PartnerLogController.log(BIDDER_INFO_FETCH_SUCCEEDED) - return emptyMap() + return Result.success(emptyMap()) } /** @@ -300,36 +217,34 @@ class AdMobAdapter : PartnerAdapter { ): Result { PartnerLogController.log(LOAD_STARTED) - return when (request.format.key) { - AdFormat.INTERSTITIAL.key -> + return when (request.format) { + PartnerAdFormats.INTERSTITIAL -> loadInterstitialAd( context, request, partnerAdListener, ) - AdFormat.REWARDED.key -> + PartnerAdFormats.REWARDED -> loadRewardedAd( context, request, partnerAdListener, ) - AdFormat.BANNER.key, "adaptive_banner" -> + PartnerAdFormats.BANNER -> loadBannerAd( context, request, partnerAdListener, ) + PartnerAdFormats.REWARDED_INTERSTITIAL -> + loadRewardedInterstitialAd( + context, + request, + partnerAdListener, + ) else -> { - if (request.format.key == "rewarded_interstitial") { - loadRewardedInterstitialAd( - context, - request, - partnerAdListener, - ) - } else { - PartnerLogController.log(LOAD_FAILED) - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_LOAD_FAILURE_UNSUPPORTED_AD_FORMAT)) - } + PartnerLogController.log(LOAD_FAILED) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.LoadError.UnsupportedAdFormat)) } } } @@ -337,29 +252,26 @@ class AdMobAdapter : PartnerAdapter { /** * Attempt to show the currently loaded AdMob ad. * - * @param context The current [Context] + * @param activity The current [Activity] * @param partnerAd The [PartnerAd] object containing the AdMob ad to be shown. * * @return Result.success(PartnerAd) if the ad was successfully shown, Result.failure(Exception) otherwise. */ override suspend fun show( - context: Context, + activity: Activity, partnerAd: PartnerAd, ): Result { PartnerLogController.log(SHOW_STARTED) val listener = listeners.remove(partnerAd.request.identifier) - return when (partnerAd.request.format.key) { - AdFormat.BANNER.key, "adaptive_banner" -> showBannerAd(partnerAd) - AdFormat.INTERSTITIAL.key -> showInterstitialAd(context, partnerAd, listener) - AdFormat.REWARDED.key -> showRewardedAd(context, partnerAd, listener) + return when (partnerAd.request.format) { + PartnerAdFormats.BANNER -> showBannerAd(partnerAd) + PartnerAdFormats.INTERSTITIAL -> showInterstitialAd(activity, partnerAd, listener) + PartnerAdFormats.REWARDED -> showRewardedAd(activity, partnerAd, listener) + PartnerAdFormats.REWARDED_INTERSTITIAL -> showRewardedInterstitialAd(activity, partnerAd, listener) else -> { - if (partnerAd.request.format.key == "rewarded_interstitial") { - showRewardedInterstitialAd(context, partnerAd, listener) - } else { - PartnerLogController.log(SHOW_FAILED) - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_SHOW_FAILURE_UNSUPPORTED_AD_FORMAT)) - } + PartnerLogController.log(SHOW_FAILED) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.ShowError.UnsupportedAdFormat)) } } } @@ -376,8 +288,8 @@ class AdMobAdapter : PartnerAdapter { listeners.remove(partnerAd.request.identifier) // Only invalidate banners as there are no explicit methods to invalidate the other formats. - return when (partnerAd.request.format.key) { - AdFormat.BANNER.key, "adaptive_banner" -> destroyBannerAd(partnerAd) + return when (partnerAd.request.format) { + PartnerAdFormats.BANNER -> destroyBannerAd(partnerAd) else -> { PartnerLogController.log(INVALIDATE_SUCCEEDED) Result.success(partnerAd) @@ -385,6 +297,54 @@ class AdMobAdapter : PartnerAdapter { } } + override fun setConsents( + context: Context, + consents: Map, + modifiedKeys: Set, + ) { + val consent = consents[configuration.partnerId]?.takeIf { it.isNotBlank() } + ?: consents[ConsentKeys.GDPR_CONSENT_GIVEN]?.takeIf { it.isNotBlank() } + consent?.let { + when(it) { + ConsentValues.GRANTED -> { + PartnerLogController.log(PartnerLogController.PartnerAdapterEvents.GDPR_CONSENT_GRANTED) + allowPersonalizedAds = true + } + + ConsentValues.DENIED -> { + PartnerLogController.log(PartnerLogController.PartnerAdapterEvents.GDPR_CONSENT_DENIED) + allowPersonalizedAds = false + } + + else -> { + PartnerLogController.log(PartnerLogController.PartnerAdapterEvents.GDPR_CONSENT_UNKNOWN) + } + } + } + + consents[ConsentKeys.USP]?.let { + ccpaPrivacyString = it + } + + consents[ConsentKeys.CCPA_OPT_IN]?.let { + when(it) { + ConsentValues.GRANTED -> { + PartnerLogController.log(PartnerLogController.PartnerAdapterEvents.USP_CONSENT_GRANTED) + restrictedDataProcessingEnabled = true + } + + ConsentValues.DENIED -> { + PartnerLogController.log(PartnerLogController.PartnerAdapterEvents.USP_CONSENT_DENIED) + restrictedDataProcessingEnabled = false + } + + else -> { + PartnerLogController.log(CUSTOM, "Unable to set RDP since CCPA_OPT_IN is $it") + } + } + } + } + /** * Get a [Result] containing the initialization result of the Google Mobile Ads SDK. * @@ -392,20 +352,21 @@ class AdMobAdapter : PartnerAdapter { * * @return A [Result] object containing details about the initialization result. */ - private fun getInitResult(status: AdapterStatus?): Result { - return status?.let { it -> + private fun getInitResult(status: AdapterStatus?): Result> { + return status?.let { if (it.initializationState == AdapterStatus.State.READY) { - Result.success(PartnerLogController.log(SETUP_SUCCEEDED)) + PartnerLogController.log(SETUP_SUCCEEDED) + Result.success(emptyMap()) } else { PartnerLogController.log( SETUP_FAILED, "Initialization state: ${it.initializationState}. Description: ${it.description}", ) - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_INITIALIZATION_FAILURE_UNKNOWN)) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.InitializationError.Unknown)) } } ?: run { PartnerLogController.log(SETUP_FAILED, "Initialization status is null.") - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_INITIALIZATION_FAILURE_UNKNOWN)) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.InitializationError.Unknown)) } } @@ -429,10 +390,14 @@ class AdMobAdapter : PartnerAdapter { CoroutineScope(Main).launch { val adview = AdView(context) - val adSize = getAdMobAdSize(context, request.size, request.format.key == "adaptive_banner") + val adSize = getAdMobAdSize( + context, + request.bannerSize?.asSize(), + request.bannerSize?.isAdaptive ?: false, + ) val details = - if (request.format.key == "adaptive_banner") { + if (request.bannerSize?.isAdaptive == true) { mapOf( "banner_width_dips" to "${adSize.width}", "banner_height_dips" to "${adSize.height}", @@ -465,7 +430,21 @@ class AdMobAdapter : PartnerAdapter { override fun onAdLoaded() { PartnerLogController.log(LOAD_SUCCEEDED) - resumeOnce(Result.success(partnerAd)) + resumeOnce(Result.success( + PartnerAd( + ad = adview, + details = details, + request = request, + partnerBannerSize = PartnerBannerSize( + Size(adSize.width, adSize.height), + if (request.bannerSize?.isAdaptive == true) { + BannerTypes.ADAPTIVE_BANNER + } else { + BannerTypes.BANNER + }, + ) + ) + )) } override fun onAdFailedToLoad(adError: LoadAdError) { @@ -715,29 +694,24 @@ class AdMobAdapter : PartnerAdapter { Result.success(partnerAd) } ?: run { PartnerLogController.log(SHOW_FAILED, "Banner ad is null.") - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_SHOW_FAILURE_AD_NOT_FOUND)) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.ShowError.AdNotFound)) } } /** * Attempt to show an AdMob interstitial ad on the main thread. * - * @param context The current [Context]. + * @param activity The current [Activity]. * @param partnerAd The [PartnerAd] object containing the AdMob ad to be shown. - * @param listener A [PartnerAdListener] to notify Helium of ad events. + * @param listener A [PartnerAdListener] to notify Chartboost Mediation of ad events. * * @return Result.success(PartnerAd) if the ad was successfully shown, Result.failure(Exception) otherwise. */ private suspend fun showInterstitialAd( - context: Context, + activity: Activity, partnerAd: PartnerAd, listener: PartnerAdListener?, ): Result { - if (context !is Activity) { - PartnerLogController.log(SHOW_FAILED, "Context is not an Activity.") - return Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_SHOW_FAILURE_ACTIVITY_NOT_FOUND)) - } - return suspendCancellableCoroutine { continuation -> fun resumeOnce(result: Result) { if (continuation.isActive) { @@ -755,14 +729,14 @@ class AdMobAdapter : PartnerAdapter { partnerAd, WeakReference(continuation), ) - interstitial.show(context) + interstitial.show(activity) } } ?: run { PartnerLogController.log(SHOW_FAILED, "Ad is null.") resumeOnce( Result.failure( ChartboostMediationAdException( - ChartboostMediationError.CM_SHOW_FAILURE_AD_NOT_FOUND, + ChartboostMediationError.ShowError.AdNotFound, ), ), ) @@ -773,22 +747,17 @@ class AdMobAdapter : PartnerAdapter { /** * Attempt to show an AdMob rewarded ad on the main thread. * - * @param context The current [Context]. + * @param activity The current [Activity]. * @param partnerAd The [PartnerAd] object containing the AdMob ad to be shown. * @param listener A [PartnerAdListener] to notify Chartboost Mediation of ad events. * * @return Result.success(PartnerAd) if the ad was successfully shown, Result.failure(Exception) otherwise. */ private suspend fun showRewardedAd( - context: Context, + activity: Activity, partnerAd: PartnerAd, listener: PartnerAdListener?, ): Result { - if (context !is Activity) { - PartnerLogController.log(SHOW_FAILED, "Context is not an Activity.") - return Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_SHOW_FAILURE_ACTIVITY_NOT_FOUND)) - } - return suspendCancellableCoroutine { continuation -> fun resumeOnce(result: Result) { if (continuation.isActive) { @@ -807,7 +776,7 @@ class AdMobAdapter : PartnerAdapter { WeakReference(continuation), ) - rewardedAd.show(context) { + rewardedAd.show(activity) { PartnerLogController.log(DID_REWARD) listener?.onPartnerAdRewarded(partnerAd) ?: PartnerLogController.log( @@ -821,7 +790,7 @@ class AdMobAdapter : PartnerAdapter { resumeOnce( Result.failure( ChartboostMediationAdException( - ChartboostMediationError.CM_SHOW_FAILURE_AD_NOT_FOUND, + ChartboostMediationError.ShowError.AdNotFound, ), ), ) @@ -832,22 +801,17 @@ class AdMobAdapter : PartnerAdapter { /** * Attempt to show an AdMob rewarded interstitial ad on the main thread. * - * @param context The current [Context]. + * @param activity The current [Activity]. * @param partnerAd The [PartnerAd] object containing the AdMob ad to be shown. * @param listener A [PartnerAdListener] to notify Chartboost Mediation of ad events. * * @return Result.success(PartnerAd) if the ad was successfully shown, Result.failure(Exception) otherwise. */ private suspend fun showRewardedInterstitialAd( - context: Context, + activity: Activity, partnerAd: PartnerAd, listener: PartnerAdListener?, ): Result { - if (context !is Activity) { - PartnerLogController.log(SHOW_FAILED, "Context is not an Activity.") - return Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_SHOW_FAILURE_ACTIVITY_NOT_FOUND)) - } - return suspendCancellableCoroutine { continuation -> fun resumeOnce(result: Result) { if (continuation.isActive) { @@ -866,7 +830,7 @@ class AdMobAdapter : PartnerAdapter { WeakReference(continuation), ) - rewardedInterstitialAd.show(context) { + rewardedInterstitialAd.show(activity) { PartnerLogController.log(DID_REWARD) listener?.onPartnerAdRewarded(partnerAd) ?: PartnerLogController.log( @@ -880,7 +844,7 @@ class AdMobAdapter : PartnerAdapter { resumeOnce( Result.failure( ChartboostMediationAdException( - ChartboostMediationError.CM_SHOW_FAILURE_AD_NOT_FOUND, + ChartboostMediationError.ShowError.AdNotFound, ), ), ) @@ -905,11 +869,11 @@ class AdMobAdapter : PartnerAdapter { Result.success(partnerAd) } else { PartnerLogController.log(INVALIDATE_FAILED, "Ad is not an AdView.") - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_INVALIDATE_FAILURE_WRONG_RESOURCE_TYPE)) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.InvalidateError.WrongResourceType)) } } ?: run { PartnerLogController.log(INVALIDATE_FAILED, "Ad is null.") - Result.failure(ChartboostMediationAdException(ChartboostMediationError.CM_INVALIDATE_FAILURE_AD_NOT_FOUND)) + Result.failure(ChartboostMediationAdException(ChartboostMediationError.InvalidateError.AdNotFound)) } } @@ -925,7 +889,7 @@ class AdMobAdapter : PartnerAdapter { identifier: String, isHybridSetup: Boolean, ): AdRequest { - val extras = buildPrivacyConsents() + val extras = Bundle() if (isHybridSetup) { // Requirement by Google for their debugging purposes @@ -935,28 +899,21 @@ class AdMobAdapter : PartnerAdapter { extras.putString("platform_name", "chartboost") - return AdRequest.Builder() - .addNetworkExtrasBundle(AdMobAdapter::class.java, extras) - .build() - } + if (!allowPersonalizedAds) { + extras.putString("npa", "1") + } - /** - * Build a [Bundle] containing privacy settings for the current ad request for AdMob. - * - * @return A [Bundle] containing privacy settings for the current ad request for AdMob. - */ - private fun buildPrivacyConsents(): Bundle { - return Bundle().apply { - if (gdprApplies == true && !allowPersonalizedAds) { - putString("npa", "1") - } + if (restrictedDataProcessingEnabled) { + extras.putInt("rdp", 1) + } - ccpaPrivacyString?.let { - if (it.isNotEmpty()) { - putString("IABUSPrivacy_String", it) - } - } + ccpaPrivacyString?.takeIf { it.isNotBlank() }.let { + extras.putString("IABUSPrivacy_String", ccpaPrivacyString) } + + return AdRequest.Builder() + .addNetworkExtrasBundle(AdMobAdapter::class.java, extras) + .build() } /** @@ -966,8 +923,8 @@ class AdMobAdapter : PartnerAdapter { * * @return True if this waterfall is a hybrid setup, false otherwise. */ - private fun getIsHybridSetup(settings: Map): Boolean { - return settings[IS_HYBRID_SETUP]?.toBoolean() ?: false + private fun getIsHybridSetup(settings: Map): Boolean { + return (settings[IS_HYBRID_SETUP] as? String?)?.toBoolean() ?: false } } diff --git a/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapterConfiguration.kt b/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapterConfiguration.kt new file mode 100644 index 0000000..5c13a1b --- /dev/null +++ b/AdMobAdapter/src/main/java/com/chartboost/mediation/admobadapter/AdMobAdapterConfiguration.kt @@ -0,0 +1,67 @@ +package com.chartboost.mediation.admobadapter + +import com.chartboost.chartboostmediationsdk.domain.PartnerAdapterConfiguration +import com.chartboost.chartboostmediationsdk.utils.PartnerLogController +import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.RequestConfiguration +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object AdMobAdapterConfiguration : PartnerAdapterConfiguration { + /** + * The partner name for internal uses. + */ + override val partnerId = "admob" + + /** + * The partner name for external uses. + */ + override val partnerDisplayName = "AdMob" + + /** + * The partner SDK version. + */ + override val partnerSdkVersion = MobileAds.getVersion().toString() + + /** + * The partner adapter version. + * + * You may version the adapter using any preferred convention, but it is recommended to apply the + * following format if the adapter will be published by Chartboost Mediation: + * + * Chartboost Mediation.Partner.Adapter + * + * "Chartboost Mediation" represents the Chartboost Mediation SDK’s major version that is compatible with this adapter. This must be 1 digit. + * "Partner" represents the partner SDK’s major.minor.patch.x (where x is optional) version that is compatible with this adapter. This can be 3-4 digits. + * "Adapter" represents this adapter’s version (starting with 0), which resets to 0 when the partner SDK’s version changes. This must be 1 digit. + */ + override val adapterVersion = BuildConfig.CHARTBOOST_MEDIATION_ADMOB_ADAPTER_VERSION + + /** + * List containing device IDs to be set for enabling AdMob test ads. It can be populated at + * any time and will take effect for the next ad request. Remember to empty this list or + * stop setting it before releasing your app. + */ + var testDeviceIds = listOf() + set(value) { + field = value + PartnerLogController.log( + PartnerLogController.PartnerAdapterEvents.CUSTOM, + "AdMob test device ID(s) to be set: ${ + if (value.isEmpty()) { + "none" + } else { + value.joinToString() + } + }", + ) + + // There have been known ANRs when calling setRequestConfiguration() on the main thread. + CoroutineScope(Dispatchers.IO).launch { + MobileAds.setRequestConfiguration( + RequestConfiguration.Builder().setTestDeviceIds(value).build(), + ) + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 63fdd1f..b06dd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Note the first digit of every adapter version corresponds to the major version of the Chartboost Mediation SDK compatible with that adapter. Adapters are compatible with any Chartboost Mediation SDK version within that major version. +### 5.23.1.0.0 +- This version of the adapter has been certified with Google Mobile Ads SDK 23.1.0. +- This version of the adapter supports Chartboost Mediation SDK version 5.+. + ### 4.22.3.0.6 - Initialization of `MobileAds` SDK on the `IO` context. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cdb3176..d60b66d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ Follow these guides to create a detailed and effective report for us to understa * **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened. ### Pull Requests -In order to submit pull requests, you are required to review and sign the [Contribution License Agreement (CLA)](https://developers.chartboost.com/docs/mediation-contribution-license-agreement) which is available on the Chartboost website to view. Once you have read the agreement, sign the appropriate form depending on whether you are an individual an employer contributor. +In order to submit pull requests, you are required to review and sign the [Contribution License Agreement (CLA)](https://docs.chartboost.com/en/partners/contribution-license-agreement/) which is available on the Chartboost website to view. Once you have read the agreement, sign the appropriate form depending on whether you are an individual an employer contributor. - [Individual contributor license agreement form](https://na3.docusign.net/Member/PowerFormSigning.aspx?PowerFormId=159c66e8-610c-4afc-9330-15bc2217c291&env=na3&acct=9c982e12-8675-45df-9d81-95fe3656e695&v=2). _You wish to contribute on your own behalf as an individual._ @@ -43,7 +43,7 @@ _You wish to contribute on behalf of your employer._ #### Submitting a Pull Request Follow these steps to have your contribution considered by the maintainers: -1. Review and sign the [Contribution License Agreement (CLA)](https://developers.chartboost.com/docs/mediation-contribution-license-agreement). +1. Review and sign the [Contribution License Agreement (CLA)](https://docs.chartboost.com/en/partners/contribution-license-agreement/). 2. Identify the issue related to your fix. If an issue doesn't exist, then create a new issue. 3. Create a pull request. 4. Format the title starting with the issue number, followed by a brief description of the fox. _Example: `[ISSUE-60] Fix null pointer exception`._ diff --git a/README.md b/README.md index 2179288..55e8261 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,15 @@ The Chartboost Mediation AdMob adapter mediates AdMob via the Chartboost Mediati ## Minimum Requirements | Plugin | Version | -| ------------------------ | ------- | -| Chartboost Mediation SDK | 4.0.0+ | +| ------------------------ |---------| +| Chartboost Mediation SDK | 5.0.0+ | | Android API | 21+ | ## Integration In your `build.gradle`, add the following entry: ``` - implementation "com.chartboost:chartboost-mediation-adapter-admob:4.22.3.0.6" + implementation "com.chartboost:chartboost-mediation-adapter-admob:5.23.1.0.0" ``` ## Contributions diff --git a/build.gradle.kts b/build.gradle.kts index a40a897..78fa416 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ /* * Copyright 2022-2024 Chartboost, Inc. - * + * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE file. */ @@ -13,9 +13,9 @@ buildscript { } plugins { - id("com.android.application") version "7.4.1" apply false - id("com.android.library") version "7.4.1" apply false - id("org.jetbrains.kotlin.android") version "1.7.20" apply false + id("com.android.application") version "8.2.2" apply false + id("com.android.library") version "8.2.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.21" apply false } task("clean") { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8e47e68..b01990d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Jun 23 19:36:48 EDT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index 8b85d23..64776dc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ /* * Copyright 2022-2024 Chartboost, Inc. - * + * * Use of this source code is governed by an MIT-style * license that can be found in the LICENSE file. */ @@ -16,4 +16,4 @@ pluginManagement { rootProject.name = "AdMobAdapter" include(":AdMobAdapter") include(":android-helium-sdk") -include(":Helium") +include(":ChartboostMediation")