Skip to content

Commit

Permalink
Implement RTB Banner Ad loading for Line Mediation Adapter.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 689828858
  • Loading branch information
Mobile Ads Developer Relations authored and copybara-github committed Oct 25, 2024
1 parent cf58d2e commit 6c1622b
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 23 deletions.
1 change: 1 addition & 0 deletions ThirdPartyAdapters/line/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## LINE Android Mediation Adapter Changelog

#### Next Version
- Implemented AdLoader to enable RTB for Banner Ads.
- Implemented AdLoader to enable RTB for Interstitial Ads.
- Implemented AdLoader to enable RTB for Rewarded Ads.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.View
import com.five_corp.ad.AdLoader
import com.five_corp.ad.BidData
import com.five_corp.ad.FiveAdConfig
import com.five_corp.ad.FiveAdCustomLayout
import com.five_corp.ad.FiveAdCustomLayoutEventListener
import com.five_corp.ad.FiveAdErrorCode
import com.five_corp.ad.FiveAdInterface
import com.five_corp.ad.FiveAdLoadListener
import com.google.ads.mediation.line.LineExtras.Companion.KEY_ENABLE_AD_SOUND
import com.google.ads.mediation.line.LineMediationAdapter.Companion.SDK_ERROR_DOMAIN
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.MediationUtils
Expand All @@ -40,27 +44,72 @@ class LineBannerAd
private constructor(
private val context: Context,
private val appId: String,
private val slotId: String?,
private val bidResponse: String,
private val watermark: String,
private val mediationAdLoadCallback:
MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
private val adView: FiveAdCustomLayout,
private val adSize: AdSize,
private val networkExtras: Bundle?,
) : MediationBannerAd, FiveAdLoadListener, FiveAdCustomLayoutEventListener {

private var mediationBannerAdCallback: MediationBannerAdCallback? = null
private lateinit var adView: FiveAdCustomLayout

fun loadAd() {
if (slotId.isNullOrEmpty()) {
val adError =
AdError(
LineMediationAdapter.ERROR_CODE_MISSING_SLOT_ID,
LineMediationAdapter.ERROR_MSG_MISSING_SLOT_ID,
LineMediationAdapter.ADAPTER_ERROR_DOMAIN,
)
mediationAdLoadCallback.onFailure(adError)
return
}
LineInitializer.initialize(context, appId)
// FiveAd SDK requires the size of the banner given in pixels.
adView =
LineSdkFactory.delegate.createFiveAdCustomLayout(
context,
slotId,
adSize.getWidthInPixels(context),
)
adView.setLoadListener(this)
if (networkExtras != null) {
adView.enableSound(networkExtras.getBoolean(KEY_ENABLE_AD_SOUND, false))
}
adView.loadAdAsync()
}

fun loadRtbAd() {
val fiveAdConfig = FiveAdConfig(appId)
val adLoader = AdLoader.getAdLoader(context, fiveAdConfig) ?: return
val bidData = BidData(bidResponse, watermark)
adLoader.loadBannerAd(
bidData,
object : AdLoader.LoadBannerAdCallback {
override fun onLoad(fiveAdCustomLayout: FiveAdCustomLayout) {
adView = fiveAdCustomLayout
if (networkExtras != null) {
adView.enableSound(networkExtras.getBoolean(KEY_ENABLE_AD_SOUND, false))
}
adView.setEventListener(this@LineBannerAd)
mediationBannerAdCallback = mediationAdLoadCallback.onSuccess(this@LineBannerAd)
}

override fun onError(adErrorCode: FiveAdErrorCode) {
val adError = AdError(adErrorCode.value, adErrorCode.name, SDK_ERROR_DOMAIN)
mediationAdLoadCallback.onFailure(adError)
}
},
)
}

override fun getView(): View = adView

override fun onFiveAdLoad(ad: FiveAdInterface) {
// This callback is not used in the RTB flow.
Log.d(TAG, "Finished loading Line Banner Ad for slotId: ${ad.slotId}")
val loadedAd = ad as? FiveAdCustomLayout
loadedAd?.let {
Expand Down Expand Up @@ -97,11 +146,12 @@ private constructor(
}

override fun onFiveAdLoadError(ad: FiveAdInterface, errorCode: FiveAdErrorCode) {
// This callback is not used in the RTB flow.
val adError =
AdError(
errorCode.value,
String.format(LineMediationAdapter.ERROR_MSG_AD_LOADING, errorCode.name),
LineMediationAdapter.SDK_ERROR_DOMAIN,
SDK_ERROR_DOMAIN,
)
Log.w(TAG, adError.message)
mediationAdLoadCallback.onFailure(adError)
Expand Down Expand Up @@ -183,29 +233,17 @@ private constructor(
}

val slotId = serverParameters.getString(LineMediationAdapter.KEY_SLOT_ID)
if (slotId.isNullOrEmpty()) {
val adError =
AdError(
LineMediationAdapter.ERROR_CODE_MISSING_SLOT_ID,
LineMediationAdapter.ERROR_MSG_MISSING_SLOT_ID,
LineMediationAdapter.ADAPTER_ERROR_DOMAIN,
)
mediationAdLoadCallback.onFailure(adError)
return Result.failure(NoSuchElementException(adError.message))
}
// FiveAd SDK requires the size of the banner given in pixels.
val bannerAdView =
LineSdkFactory.delegate.createFiveAdCustomLayout(
context,
slotId,
adSize.getWidthInPixels(context),
)
val bidResponse = mediationBannerAdConfiguration.bidResponse
val watermark = mediationBannerAdConfiguration.watermark

return Result.success(
LineBannerAd(
context,
appId,
slotId,
bidResponse,
watermark,
mediationAdLoadCallback,
bannerAdView,
adSize,
mediationBannerAdConfiguration.mediationExtras,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ class LineMediationAdapter : RtbAdapter() {
adConfiguration: MediationBannerAdConfiguration,
callback: MediationAdLoadCallback<MediationBannerAd, MediationBannerAdCallback>,
) {
super.loadRtbBannerAd(adConfiguration, callback)
LineBannerAd.newInstance(adConfiguration, callback).onSuccess {
bannerAd = it
bannerAd.loadRtbAd()
}
}

override fun loadRtbInterstitialAd(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class LineBannerAdTest {

@Test
fun getView_returnsCreatedBannerAd() {
lineBannerAd.loadAd()

val createdAdView = lineBannerAd.view

assertThat(createdAdView).isEqualTo(mockFiveAdCustomLayout)
Expand All @@ -89,6 +91,8 @@ class LineBannerAdTest {

@Test
fun onFiveAdLoad_invokesOnSuccess() {
lineBannerAd.loadAd()

lineBannerAd.onFiveAdLoad(mockFiveAdCustomLayout)

verify(mockFiveAdCustomLayout).setEventListener(lineBannerAd)
Expand All @@ -110,6 +114,7 @@ class LineBannerAdTest {

@Test
fun onClick_invokesReportAdClickedAndOnAdLeftApplication() {
lineBannerAd.loadAd()
lineBannerAd.onFiveAdLoad(mockFiveAdCustomLayout)

lineBannerAd.onClick(mockFiveAdCustomLayout)
Expand All @@ -120,6 +125,7 @@ class LineBannerAdTest {

@Test
fun onImpression_invokesReportAdImpression() {
lineBannerAd.loadAd()
lineBannerAd.onFiveAdLoad(mockFiveAdCustomLayout)

lineBannerAd.onImpression(mockFiveAdCustomLayout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ class LineMediationAdapterTest {
private val activity: Activity = Robolectric.buildActivity(Activity::class.java).get()
private val mockSdkWrapper = mock<SdkWrapper>()
private val fiveAdConfig = FiveAdConfig(TEST_APP_ID_1)
private val mockFiveAdCustomLayout = mock<FiveAdCustomLayout>()
private val mockFiveAdCustomLayout =
mock<FiveAdCustomLayout> {
on { logicalWidth } doReturn AdSize.BANNER.width
on { logicalHeight } doReturn AdSize.BANNER.height
}
private val mockFiveAdInterstitial = mock<FiveAdInterstitial>()
private val mockFiveAdVideoReward = mock<FiveAdVideoReward>()
private val mockFiveAdNative = mock<FiveAdNative>()
Expand Down Expand Up @@ -600,10 +604,11 @@ class LineMediationAdapterTest {
serverParameters: Bundle = bundleOf(),
adSize: AdSize = AdSize.BANNER,
mediationExtras: Bundle = bundleOf(),
bidResponse: String = "",
) =
MediationBannerAdConfiguration(
context,
/*bidresponse=*/ "",
bidResponse,
serverParameters,
mediationExtras,
/*isTesting=*/ true,
Expand All @@ -617,6 +622,106 @@ class LineMediationAdapterTest {

// endregion

// region RTB Banner Ad Tests
@Test
fun loadRtbBannerAd_withNullAppId_invokesOnFailure() {
val serverParameters = bundleOf()
val mediationBannerAdConfiguration =
createMediationBannerAdConfiguration(serverParameters, bidResponse = TEST_BID_RESPONSE)

lineMediationAdapter.loadRtbBannerAd(
mediationBannerAdConfiguration,
mockMediationBannerAdLoadCallback,
)

val expectedAdError =
AdError(
LineMediationAdapter.ERROR_CODE_MISSING_APP_ID,
LineMediationAdapter.ERROR_MSG_MISSING_APP_ID,
ADAPTER_ERROR_DOMAIN,
)
verify(mockMediationBannerAdLoadCallback).onFailure(argThat(AdErrorMatcher(expectedAdError)))
}

@Test
fun loadRtbBannerAd_withEmptyAppId_invokesOnFailure() {
val serverParameters = bundleOf(KEY_APP_ID to "")
val mediationBannerAdConfiguration =
createMediationBannerAdConfiguration(
serverParameters = serverParameters,
bidResponse = TEST_BID_RESPONSE,
)

lineMediationAdapter.loadRtbBannerAd(
mediationBannerAdConfiguration,
mockMediationBannerAdLoadCallback,
)

val expectedAdError =
AdError(
LineMediationAdapter.ERROR_CODE_MISSING_APP_ID,
LineMediationAdapter.ERROR_MSG_MISSING_APP_ID,
ADAPTER_ERROR_DOMAIN,
)
verify(mockMediationBannerAdLoadCallback).onFailure(argThat(AdErrorMatcher(expectedAdError)))
}

@Test
fun loadRtbBannerAd_createsAndLoadsCustomLayout() {
mockStatic(AdLoader::class.java).use {
val mockAdLoader = mock<AdLoader>()
whenever(AdLoader.getAdLoader(eq(context), any())) doReturn mockAdLoader
val serverParameters = bundleOf(KEY_APP_ID to TEST_APP_ID_1)
val mediationBannerAdConfiguration =
createMediationBannerAdConfiguration(
serverParameters = serverParameters,
bidResponse = TEST_BID_RESPONSE,
)

lineMediationAdapter.loadRtbBannerAd(
mediationBannerAdConfiguration,
mockMediationBannerAdLoadCallback,
)

val loadCallbackCaptor = argumentCaptor<AdLoader.LoadBannerAdCallback>()
verify(mockAdLoader).loadBannerAd(any(), loadCallbackCaptor.capture())
val capturedCallback = loadCallbackCaptor.firstValue
capturedCallback.onLoad(mockFiveAdCustomLayout)
verify(mockFiveAdCustomLayout).setEventListener(isA<LineBannerAd>())
verify(mockFiveAdCustomLayout).enableSound(false)
verify(mockMediationBannerAdLoadCallback).onSuccess(isA<LineBannerAd>())
}
}

@Test
fun loadRtbBannerAd_withExtras_modifiesEnablesSound() {
mockStatic(AdLoader::class.java).use {
val mockAdLoader = mock<AdLoader>()
whenever(AdLoader.getAdLoader(eq(context), any())) doReturn mockAdLoader
val serverParameters = bundleOf(KEY_APP_ID to TEST_APP_ID_1)
val mediationExtras = bundleOf(KEY_ENABLE_AD_SOUND to true)
val mediationBannerAdConfiguration =
createMediationBannerAdConfiguration(
serverParameters = serverParameters,
mediationExtras = mediationExtras,
bidResponse = TEST_BID_RESPONSE,
)

lineMediationAdapter.loadRtbBannerAd(
mediationBannerAdConfiguration,
mockMediationBannerAdLoadCallback,
)

val loadCallbackCaptor = argumentCaptor<AdLoader.LoadBannerAdCallback>()
verify(mockAdLoader).loadBannerAd(any(), loadCallbackCaptor.capture())
val capturedCallback = loadCallbackCaptor.firstValue
capturedCallback.onLoad(mockFiveAdCustomLayout)
verify(mockFiveAdCustomLayout).enableSound(true)
}
}

// endregion

// region Interstitial Ad Tests
@Test
fun loadInterstitialAd_withNonActivityContext_invokesOnFailure() {
Expand Down

0 comments on commit 6c1622b

Please sign in to comment.