Skip to content

Commit 2fd7c04

Browse files
Merge pull request #692 from fyber-engineering:AND-6739-Implement-Native-Ads-adapter-for-AdMob-
PiperOrigin-RevId: 834464208
2 parents 4c163da + 484f757 commit 2fd7c04

File tree

6 files changed

+608
-0
lines changed

6 files changed

+608
-0
lines changed

ThirdPartyAdapters/dtexchange/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## DT Exchange Android Mediation Adapter Changelog
22

3+
#### Next Version
4+
- Added support for Native Ads
5+
36
#### Version 8.4.0.0
47
- Removed class-level references to Context. Can help reduce memory leak issues.
58
- Verified compatibility with DT Exchange SDK 8.4.0.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.ads.mediation.fyber
16+
17+
import android.content.Context
18+
import android.net.Uri
19+
import android.util.Log
20+
import android.view.View
21+
import android.view.ViewGroup
22+
import com.fyber.inneractive.sdk.external.InneractiveAdManager
23+
import com.fyber.inneractive.sdk.external.InneractiveAdSpot
24+
import com.fyber.inneractive.sdk.external.InneractiveAdSpotManager
25+
import com.fyber.inneractive.sdk.external.InneractiveErrorCode
26+
import com.fyber.inneractive.sdk.external.MediaView
27+
import com.fyber.inneractive.sdk.external.NativeAdContent
28+
import com.fyber.inneractive.sdk.external.NativeAdEventsListener
29+
import com.fyber.inneractive.sdk.external.VideoContentListener
30+
import com.google.android.gms.ads.MobileAds
31+
import com.google.android.gms.ads.mediation.MediationAdLoadCallback
32+
import com.google.android.gms.ads.mediation.MediationNativeAdCallback
33+
import com.google.android.gms.ads.mediation.MediationNativeAdConfiguration
34+
import com.google.android.gms.ads.mediation.NativeAdMapper
35+
import com.google.android.gms.ads.nativead.NativeAd
36+
import com.google.android.gms.ads.nativead.NativeAdAssetNames
37+
38+
class DTExchangeNativeAdMapper(
39+
private val adLoadCallback: MediationAdLoadCallback<NativeAdMapper, MediationNativeAdCallback>
40+
) : NativeAdMapper() {
41+
42+
private var mediationNativeAdCallback: MediationNativeAdCallback? = null
43+
private var nativeAdSpot: InneractiveAdSpot? = null
44+
private var adContent: NativeAdContent? = null
45+
46+
fun loadAd(mediationNativeAdConfiguration: MediationNativeAdConfiguration) {
47+
InneractiveAdManager.setMediationName(FyberMediationAdapter.MEDIATOR_NAME)
48+
InneractiveAdManager.setMediationVersion(MobileAds.getVersion().toString())
49+
50+
val bidResponse = mediationNativeAdConfiguration.bidResponse
51+
52+
nativeAdSpot =
53+
InneractiveAdSpotManager.get().createSpot().apply {
54+
val controller = FyberFactory.createNativeAdUnitController()
55+
56+
val nativeAdVideoContentController = FyberFactory.createNativeAdVideoContentController()
57+
nativeAdVideoContentController.eventsListener =
58+
object : VideoContentListener {
59+
override fun onProgress(totalDurationInMsec: Int, positionInMsec: Int) {}
60+
61+
override fun onCompleted() {
62+
mediationNativeAdCallback?.onVideoComplete()
63+
}
64+
65+
@Deprecated("Deprecated in Java") override fun onPlayerError() {}
66+
}
67+
68+
controller.addContentController(nativeAdVideoContentController)
69+
70+
controller.eventsListener =
71+
object : NativeAdEventsListener() {
72+
override fun onAdImpression(adSpot: InneractiveAdSpot) {
73+
mediationNativeAdCallback?.onAdOpened()
74+
mediationNativeAdCallback?.reportAdImpression()
75+
}
76+
77+
override fun onAdClicked(adSpot: InneractiveAdSpot) {
78+
mediationNativeAdCallback?.reportAdClicked()
79+
mediationNativeAdCallback?.onAdOpened()
80+
}
81+
82+
override fun onAdWillCloseInternalBrowser(adSpot: InneractiveAdSpot) {}
83+
84+
override fun onAdWillOpenExternalApp(adSpot: InneractiveAdSpot) {
85+
mediationNativeAdCallback?.onAdLeftApplication()
86+
}
87+
}
88+
addUnitController(controller)
89+
setRequestListener(
90+
object : InneractiveAdSpot.NativeAdRequestListener() {
91+
override fun onInneractiveFailedAdRequest(
92+
adSpot: InneractiveAdSpot?,
93+
errorCode: InneractiveErrorCode?,
94+
) {
95+
reportErrorAndDestroy("onInneractiveFailedAdRequest error: $errorCode", errorCode)
96+
}
97+
98+
override fun onInneractiveSuccessfulNativeAdRequest(
99+
adSpot: InneractiveAdSpot?,
100+
content: NativeAdContent?,
101+
) {
102+
if (content == null) {
103+
reportErrorAndDestroy(
104+
"content is NOT NativeAdContent",
105+
InneractiveErrorCode.SDK_INTERNAL_ERROR,
106+
)
107+
return
108+
}
109+
110+
mapNativeAd(mediationNativeAdConfiguration.context, content)
111+
mediationNativeAdCallback = adLoadCallback.onSuccess(this@DTExchangeNativeAdMapper)
112+
}
113+
}
114+
)
115+
FyberAdapterUtils.updateFyberExtraParams(mediationNativeAdConfiguration.mediationExtras)
116+
loadAd(bidResponse)
117+
}
118+
}
119+
120+
override fun trackViews(
121+
containerView: View,
122+
clickableAssetViews: Map<String, View>,
123+
nonClickableAssetViews: Map<String, View>,
124+
) {
125+
containerView.tag = NativeAdContent.ViewTag.ROOT
126+
adContent?.mediaView?.tag = NativeAdContent.ViewTag.MEDIA_VIEW
127+
128+
for ((nativeAdAssetName, view) in clickableAssetViews) {
129+
view.tag = mapAssetNameToViewTag(nativeAdAssetName)
130+
}
131+
132+
adContent?.registerViewsForInteraction(
133+
containerView as ViewGroup,
134+
adContent?.mediaView,
135+
null,
136+
clickableAssetViews.values,
137+
)
138+
}
139+
140+
private fun mapAssetNameToViewTag(nativeAdAssetName: String): String {
141+
return when (nativeAdAssetName) {
142+
NativeAdAssetNames.ASSET_CALL_TO_ACTION -> NativeAdContent.ViewTag.CTA
143+
NativeAdAssetNames.ASSET_HEADLINE -> NativeAdContent.ViewTag.AD_TITLE
144+
NativeAdAssetNames.ASSET_BODY -> NativeAdContent.ViewTag.AD_DESCRIPTION
145+
NativeAdAssetNames.ASSET_ICON -> NativeAdContent.ViewTag.AD_ICON
146+
NativeAdAssetNames.ASSET_STAR_RATING -> NativeAdContent.ViewTag.RATING
147+
else -> NativeAdContent.ViewTag.OTHER
148+
}
149+
}
150+
151+
private fun reportErrorAndDestroy(message: String, errorCode: InneractiveErrorCode?) {
152+
Log.e(TAG, message)
153+
adLoadCallback.onFailure(
154+
DTExchangeErrorCodes.getAdError(errorCode ?: InneractiveErrorCode.SDK_INTERNAL_ERROR)
155+
)
156+
nativeAdSpot?.destroy()
157+
nativeAdSpot = null
158+
}
159+
160+
private fun mapNativeAd(context: Context, nativeAdContent: NativeAdContent) {
161+
val mediaView = MediaView(context)
162+
163+
nativeAdContent.bindMediaView(mediaView)
164+
adContent = nativeAdContent
165+
166+
headline = nativeAdContent.adTitle
167+
body = nativeAdContent.adDescription
168+
icon = NativeMappedImage(nativeAdContent.appIcon)
169+
callToAction = nativeAdContent.adCallToAction
170+
171+
setMediaView(nativeAdContent.mediaView)
172+
starRating = nativeAdContent.rating.toDouble()
173+
mediaContentAspectRatio = nativeAdContent.mediaAspectRatio ?: 0f
174+
175+
overrideClickHandling = true
176+
overrideImpressionRecording = true
177+
}
178+
179+
override fun destroy() {
180+
super.destroy()
181+
nativeAdSpot?.destroy()
182+
nativeAdSpot = null
183+
184+
adContent?.destroy()
185+
adContent = null
186+
187+
mediationNativeAdCallback = null
188+
}
189+
190+
class NativeMappedImage(private val uri: Uri) : NativeAd.Image() {
191+
192+
override fun getDrawable() = null
193+
194+
override fun getUri() = uri
195+
196+
override fun getScale() = 1.0
197+
}
198+
199+
private companion object {
200+
private const val TAG = "DTExchangeNativeAdMapper"
201+
}
202+
}

ThirdPartyAdapters/dtexchange/dtexchange/src/main/java/com/google/ads/mediation/fyber/FyberFactory.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package com.google.ads.mediation.fyber
1717
import com.fyber.inneractive.sdk.external.InneractiveAdSpot
1818
import com.fyber.inneractive.sdk.external.InneractiveAdSpotManager
1919
import com.fyber.inneractive.sdk.external.InneractiveFullscreenUnitController
20+
import com.fyber.inneractive.sdk.external.NativeAdUnitController
21+
import com.fyber.inneractive.sdk.external.NativeAdVideoContentController
2022

2123
object FyberFactory {
2224

@@ -29,4 +31,8 @@ object FyberFactory {
2931
fun createInneractiveFullscreenUnitController(): InneractiveFullscreenUnitController {
3032
return InneractiveFullscreenUnitController()
3133
}
34+
35+
@JvmStatic fun createNativeAdUnitController() = NativeAdUnitController()
36+
37+
@JvmStatic fun createNativeAdVideoContentController() = NativeAdVideoContentController()
3238
}

ThirdPartyAdapters/dtexchange/dtexchange/src/main/java/com/google/ads/mediation/fyber/FyberMediationAdapter.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,12 @@
5858
import com.google.android.gms.ads.mediation.MediationInterstitialAdConfiguration;
5959
import com.google.android.gms.ads.mediation.MediationInterstitialAdapter;
6060
import com.google.android.gms.ads.mediation.MediationInterstitialListener;
61+
import com.google.android.gms.ads.mediation.MediationNativeAdCallback;
62+
import com.google.android.gms.ads.mediation.MediationNativeAdConfiguration;
6163
import com.google.android.gms.ads.mediation.MediationRewardedAd;
6264
import com.google.android.gms.ads.mediation.MediationRewardedAdCallback;
6365
import com.google.android.gms.ads.mediation.MediationRewardedAdConfiguration;
66+
import com.google.android.gms.ads.mediation.NativeAdMapper;
6467
import com.google.android.gms.ads.mediation.rtb.RtbAdapter;
6568
import com.google.android.gms.ads.mediation.rtb.RtbSignalData;
6669
import com.google.android.gms.ads.mediation.rtb.SignalCallbacks;
@@ -124,6 +127,8 @@ public class FyberMediationAdapter extends RtbAdapter
124127
/** DT Exchange rewarded ad video renderer. */
125128
private FyberRewardedVideoRenderer rewardedRenderer;
126129

130+
private DTExchangeNativeAdMapper nativeAdMapper;
131+
127132
/** Default Constructor. */
128133
public FyberMediationAdapter() {}
129134

@@ -367,6 +372,11 @@ public void onDestroy() {
367372
interstitialActivityRef.clear();
368373
interstitialActivityRef = null;
369374
}
375+
376+
if (nativeAdMapper != null) {
377+
nativeAdMapper.destroy();
378+
nativeAdMapper = null;
379+
}
370380
}
371381

372382
@Override
@@ -701,4 +711,16 @@ public void loadRtbRewardedAd(
701711
InneractiveAdManager.setMediationVersion(MobileAds.getVersion().toString());
702712
rewardedRenderer.loadRtbAd(adConfiguration);
703713
}
714+
715+
@Override
716+
public void loadRtbNativeAdMapper(
717+
@NonNull MediationNativeAdConfiguration adConfiguration,
718+
@NonNull MediationAdLoadCallback<NativeAdMapper, MediationNativeAdCallback> callback) {
719+
if (nativeAdMapper != null) {
720+
nativeAdMapper.destroy();
721+
nativeAdMapper = null;
722+
}
723+
nativeAdMapper = new DTExchangeNativeAdMapper(callback);
724+
nativeAdMapper.loadAd(adConfiguration);
725+
}
704726
}

0 commit comments

Comments
 (0)