Skip to content

Instantly share code, notes, and snippets.

@thomasmso
Created March 23, 2020 20:54
Show Gist options
  • Save thomasmso/029525c04fe808110829d0dac67e06f1 to your computer and use it in GitHub Desktop.
Save thomasmso/029525c04fe808110829d0dac67e06f1 to your computer and use it in GitHub Desktop.
MAX Facebook Adapter - Android
package com.applovin.mediation.adapters;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import com.applovin.mediation.MaxAdFormat;
import com.applovin.mediation.MaxReward;
import com.applovin.mediation.adapter.MaxAdViewAdapter;
import com.applovin.mediation.adapter.MaxAdapterError;
import com.applovin.mediation.adapter.MaxInterstitialAdapter;
import com.applovin.mediation.adapter.MaxRewardedAdapter;
import com.applovin.mediation.adapter.MaxSignalProvider;
import com.applovin.mediation.adapter.listeners.MaxAdViewAdapterListener;
import com.applovin.mediation.adapter.listeners.MaxInterstitialAdapterListener;
import com.applovin.mediation.adapter.listeners.MaxRewardedAdapterListener;
import com.applovin.mediation.adapter.listeners.MaxSignalCollectionListener;
import com.applovin.mediation.adapter.parameters.MaxAdapterInitializationParameters;
import com.applovin.mediation.adapter.parameters.MaxAdapterParameters;
import com.applovin.mediation.adapter.parameters.MaxAdapterResponseParameters;
import com.applovin.mediation.adapter.parameters.MaxAdapterSignalCollectionParameters;
import com.applovin.sdk.AppLovinSdk;
import com.facebook.ads.Ad;
import com.facebook.ads.AdError;
import com.facebook.ads.AdListener;
import com.facebook.ads.AdSettings;
import com.facebook.ads.AdSize;
import com.facebook.ads.AdView;
import com.facebook.ads.AudienceNetworkAds;
import com.facebook.ads.BidderTokenProvider;
import com.facebook.ads.InterstitialAd;
import com.facebook.ads.InterstitialAdExtendedListener;
import com.facebook.ads.InterstitialAdListener;
import com.facebook.ads.RewardedVideoAd;
import com.facebook.ads.RewardedVideoAdExtendedListener;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Copyright © 2019 AppLovin Corporation. All rights reserved.
*/
public class FacebookMediationAdapter
extends MediationAdapterBase
implements MaxInterstitialAdapter, MaxRewardedAdapter, MaxAdViewAdapter, MaxSignalProvider
{
private static final AtomicBoolean INITIALIZED = new AtomicBoolean();
private static InitializationStatus sStatus;
private AdView mAdView;
private InterstitialAd mInterstitialAd;
private RewardedVideoAd mRewardedVideoAd;
private AtomicBoolean onInterstitialAdHiddenCalled = new AtomicBoolean();
private AtomicBoolean onRewardedAdVideoCompletedCalled = new AtomicBoolean();
private AtomicBoolean onRewardedAdHiddenCalled = new AtomicBoolean();
// Explicit default constructor declaration
public FacebookMediationAdapter(final AppLovinSdk sdk) { super( sdk ); }
@Override
public void initialize(final MaxAdapterInitializationParameters parameters, final Activity activity, final OnCompletionListener onCompletionListener)
{
// Check existence of SDK classes
checkExistence( InterstitialAd.class, InterstitialAdListener.class, AdSettings.class );
// Update ad settings
updateAdSettings( parameters );
if ( INITIALIZED.compareAndSet( false, true ) )
{
if ( AppLovinSdk.VERSION_CODE >= 90800 ) // 9.8.0+
{
sStatus = InitializationStatus.INITIALIZING;
}
final List<String> placementIds = parameters.getServerParameters().getStringArrayList( "placement_ids" );
final AudienceNetworkAds.InitListener initListener = new AudienceNetworkAds.InitListener()
{
@Override
public void onInitialized(final AudienceNetworkAds.InitResult initResult)
{
if ( initResult.isSuccess() )
{
log( "Facebook SDK successfully finished initialization: " + initResult.getMessage() );
if ( AppLovinSdk.VERSION_CODE >= 90800 ) // 9.8.0+
{
sStatus = InitializationStatus.INITIALIZED_SUCCESS;
onCompletionListener.onCompletion( sStatus, null );
}
else
{
onCompletionListener.onCompletion();
}
}
else
{
log( "Facebook SDK failed to finished initialization: " + initResult.getMessage() );
if ( AppLovinSdk.VERSION_CODE >= 90800 ) // 9.8.0+
{
sStatus = InitializationStatus.INITIALIZED_FAILURE;
onCompletionListener.onCompletion( sStatus, initResult.getMessage() );
}
else
{
onCompletionListener.onCompletion();
}
}
}
};
if ( parameters.isTesting() )
{
AdSettings.setDebugBuild( true );
}
log( "Initializing Facebook SDK with placements: " + placementIds );
AudienceNetworkAds.buildInitSettings( activity )
.withMediationService( getMediationIdentifier() )
.withPlacementIds( placementIds )
.withInitListener( initListener )
.initialize();
}
else
{
log( "Facebook attempted initialization already - marking initialization as completed" );
if ( AppLovinSdk.VERSION_CODE >= 90800 ) // 9.8.0+
{
onCompletionListener.onCompletion( sStatus, null );
}
else
{
onCompletionListener.onCompletion();
}
}
}
@Override
public String getSdkVersion()
{
return getVersionString( com.facebook.ads.BuildConfig.class, "VERSION_NAME" );
}
@Override
public String getAdapterVersion()
{
return com.applovin.mediation.adapters.facebook.BuildConfig.VERSION_NAME;
}
@Override
public void loadInterstitialAd(final MaxAdapterResponseParameters parameters, final Activity activity, final MaxInterstitialAdapterListener listener)
{
final String placementId = parameters.getThirdPartyAdPlacementId();
log( "Loading interstitial ad: " + placementId + "..." );
updateAdSettings( parameters );
mInterstitialAd = new InterstitialAd( activity.getApplicationContext(), placementId );
InterstitialAd.InterstitialAdLoadConfigBuilder adLoadConfigBuilder = mInterstitialAd.buildLoadAdConfig()
.withAdListener( new InterstitialAdExtendedListener()
{
@Override
public void onAdLoaded(final Ad ad)
{
log( "Interstitial ad loaded: " + placementId );
listener.onInterstitialAdLoaded();
}
@Override
public void onError(final Ad ad, final AdError adError)
{
log( "Interstitial ad (" + placementId + ") failed to load with error code (" + adError.getErrorCode() + ") and message: " + adError.getErrorMessage() );
listener.onInterstitialAdLoadFailed( toMaxError( adError ) );
}
@Override
public void onAdClicked(final Ad ad)
{
log( "Interstitial ad clicked: " + placementId );
listener.onInterstitialAdClicked();
}
@Override
public void onLoggingImpression(final Ad ad)
{
// Max does its own impression tracking
log( "Interstitial ad logging impression: " + placementId );
listener.onInterstitialAdDisplayed();
}
@Override
public void onInterstitialDisplayed(final Ad ad)
{
log( "Interstitial ad displayed: " + placementId );
// listener.onInterstitialAdDisplayed();
}
@Override
public void onInterstitialDismissed(final Ad ad)
{
log( "Interstitial ad hidden: " + placementId );
if ( onInterstitialAdHiddenCalled.compareAndSet( false, true ) )
{
listener.onInterstitialAdHidden();
}
}
@Override
public void onInterstitialActivityDestroyed()
{
log( "Interstitial ad Activity destroyed: " + placementId );
//
// This may be due to launching from app icon and having the `android:launchMode="singleTask"` flag
//
if ( onInterstitialAdHiddenCalled.compareAndSet( false, true ) )
{
listener.onInterstitialAdHidden();
}
}
@Override
public void onRewardedAdCompleted() { }
@Override
public void onRewardedAdServerSucceeded() { }
@Override
public void onRewardedAdServerFailed() { }
} );
if ( mInterstitialAd.isAdLoaded() && !mInterstitialAd.isAdInvalidated() )
{
log( "An interstitial ad has been loaded already" );
listener.onInterstitialAdLoaded();
}
else
{
// If there is no bid response
if ( TextUtils.isEmpty( parameters.getBidResponse() ) )
{
log( "Loading mediated interstitial ad..." );
mInterstitialAd.loadAd( adLoadConfigBuilder.build() );
}
// Otherwise (we have a bid response)
else
{
log( "Loading bidding interstitial ad..." );
mInterstitialAd.loadAd( adLoadConfigBuilder.withBid( parameters.getBidResponse() ).build() );
}
}
}
@Override
public void showInterstitialAd(final MaxAdapterResponseParameters parameters, final Activity activity, final MaxInterstitialAdapterListener listener)
{
log( "Showing interstitial ad: " + parameters.getThirdPartyAdPlacementId() + "..." );
if ( mInterstitialAd != null && mInterstitialAd.isAdLoaded() )
{
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad.
if ( !mInterstitialAd.isAdInvalidated() )
{
mInterstitialAd.show();
}
else
{
log( "Unable to show interstitial - ad expired..." );
listener.onInterstitialAdDisplayFailed( MaxAdapterError.AD_EXPIRED );
}
}
else
{
log( "Unable to show interstitial - no ad loaded..." );
listener.onInterstitialAdDisplayFailed( MaxAdapterError.AD_NOT_READY );
}
}
@Override
public void loadRewardedAd(final MaxAdapterResponseParameters parameters, final Activity activity, final MaxRewardedAdapterListener listener)
{
final String placementId = parameters.getThirdPartyAdPlacementId();
log( "Loading rewarded: " + placementId + "..." );
updateAdSettings( parameters );
mRewardedVideoAd = new RewardedVideoAd( activity.getApplicationContext(), placementId );
RewardedVideoAd.RewardedVideoAdLoadConfigBuilder adLoadConfigBuilder = mRewardedVideoAd.buildLoadAdConfig()
.withAdListener( new RewardedVideoAdExtendedListener()
{
private boolean hasGrantedReward;
@Override
public void onAdLoaded(final Ad ad)
{
log( "Rewarded ad loaded: " + placementId );
listener.onRewardedAdLoaded();
}
@Override
public void onError(final Ad ad, final AdError adError)
{
log( "Rewarded ad (" + placementId + ") failed to load with error code (" + adError.getErrorCode() + ") and message: " + adError.getErrorMessage() );
listener.onRewardedAdLoadFailed( toMaxError( adError ) );
}
@Override
public void onAdClicked(final Ad ad)
{
log( "Rewarded ad clicked: " + placementId );
listener.onRewardedAdClicked();
}
@Override
public void onRewardedVideoClosed()
{
log( "Rewarded ad hidden: " + placementId );
if ( onRewardedAdHiddenCalled.compareAndSet( false, true ) )
{
if ( hasGrantedReward || shouldAlwaysRewardUser() )
{
final MaxReward reward = getReward();
log( "Rewarded user with reward: " + reward );
listener.onUserRewarded( reward );
}
listener.onRewardedAdHidden();
}
}
@Override
public void onRewardedVideoCompleted()
{
log( "Rewarded ad video completed: " + placementId );
if ( onRewardedAdVideoCompletedCalled.compareAndSet( false, true ) )
{
listener.onRewardedAdVideoCompleted();
hasGrantedReward = true;
}
}
@Override
public void onLoggingImpression(final Ad ad)
{
log( "Rewarded ad logging impression: " + placementId );
listener.onRewardedAdDisplayed();
listener.onRewardedAdVideoStarted();
}
@Override
public void onRewardedVideoActivityDestroyed()
{
log( "Rewarded ad Activity destroyed: " + placementId );
//
// We will not reward the user if Activity is destroyed - this may be due to launching from app icon and having the `android:launchMode="singleTask"` flag
//
if ( onRewardedAdVideoCompletedCalled.compareAndSet( false, true ) )
{
listener.onRewardedAdVideoCompleted();
}
if ( onRewardedAdHiddenCalled.compareAndSet( false, true ) )
{
listener.onRewardedAdHidden();
}
}
} );
if ( mRewardedVideoAd.isAdLoaded() && !mRewardedVideoAd.isAdInvalidated() )
{
log( "A rewarded ad has been loaded already" );
listener.onRewardedAdLoaded();
}
else
{
// If there is no bid response
if ( TextUtils.isEmpty( parameters.getBidResponse() ) )
{
log( "Loading mediated rewarded ad..." );
mRewardedVideoAd.loadAd( adLoadConfigBuilder.build() );
}
// Otherwise (we have a bid response)
else
{
log( "Loading bidding rewarded ad..." );
mRewardedVideoAd.loadAd( adLoadConfigBuilder.withBid( parameters.getBidResponse() ).build() );
}
}
}
@Override
public void showRewardedAd(final MaxAdapterResponseParameters parameters, final Activity activity, final MaxRewardedAdapterListener listener)
{
log( "Showing rewarded ad: " + parameters.getThirdPartyAdPlacementId() + "..." );
if ( mRewardedVideoAd != null && mRewardedVideoAd.isAdLoaded() )
{
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad.
if ( !mRewardedVideoAd.isAdInvalidated() )
{
// Configure userReward from server.
configureReward( parameters );
mRewardedVideoAd.show();
}
else
{
log( "Unable to show rewarded ad - ad expired..." );
listener.onRewardedAdDisplayFailed( MaxAdapterError.AD_EXPIRED );
}
}
else
{
log( "Unable to show rewarded ad - no ad loaded..." );
listener.onRewardedAdDisplayFailed( MaxAdapterError.AD_NOT_READY );
}
}
@Override
public void collectSignal(final MaxAdapterSignalCollectionParameters parameters, final Activity activity, final MaxSignalCollectionListener callback)
{
log( "Collecting signal..." );
// Must be ran on bg thread
String signal = BidderTokenProvider.getBidderToken( activity );
callback.onSignalCollected( signal );
}
@Override
public void loadAdViewAd(final MaxAdapterResponseParameters parameters, final MaxAdFormat adFormat, final Activity activity, final MaxAdViewAdapterListener listener)
{
final String placementId = parameters.getThirdPartyAdPlacementId();
log( "Loading banner ad: " + placementId + "..." );
updateAdSettings( parameters );
mAdView = new AdView( activity, placementId, toAdSize( adFormat ) );
AdView.AdViewLoadConfigBuilder adViewLoadConfigBuilder = mAdView.buildLoadAdConfig()
.withAdListener( new AdListener()
{
@Override
public void onAdLoaded(final Ad ad)
{
log( "Banner ad loaded: " + placementId );
listener.onAdViewAdLoaded( mAdView );
}
@Override
public void onError(final Ad ad, final AdError adError)
{
log( "Banner ad (" + placementId + ") failed to load with error code (" + adError.getErrorCode() + ") and message: " + adError.getErrorMessage() );
listener.onAdViewAdLoadFailed( toMaxError( adError ) );
}
@Override
public void onAdClicked(final Ad ad)
{
log( "Banner ad clicked: " + placementId );
listener.onAdViewAdClicked();
}
@Override
public void onLoggingImpression(final Ad ad)
{
log( "Banner ad displayed: " + placementId );
listener.onAdViewAdDisplayed();
}
} );
// If there is no bid response
if ( TextUtils.isEmpty( parameters.getBidResponse() ) )
{
log( "Loading mediated banner ad..." );
mAdView.loadAd( adViewLoadConfigBuilder.build() );
}
// Otherwise (we have a bid response)
else
{
log( "Loading bidding banner ad..." );
mAdView.loadAd( adViewLoadConfigBuilder.withBid( parameters.getBidResponse() ).build() );
}
}
@Override
public void onDestroy()
{
if ( mInterstitialAd != null )
{
mInterstitialAd.destroy();
mInterstitialAd = null;
}
if ( mRewardedVideoAd != null )
{
mRewardedVideoAd.destroy();
mRewardedVideoAd = null;
}
if ( mAdView != null )
{
mAdView.destroy();
mAdView = null;
}
}
private AdSize toAdSize(final MaxAdFormat adFormat)
{
if ( adFormat == MaxAdFormat.BANNER )
{
return AdSize.BANNER_HEIGHT_50;
}
else if ( adFormat == MaxAdFormat.LEADER )
{
return AdSize.BANNER_HEIGHT_90;
}
else if ( adFormat == MaxAdFormat.MREC )
{
return AdSize.RECTANGLE_HEIGHT_250;
}
else
{
throw new IllegalArgumentException( "Invalid ad format: " + adFormat );
}
}
private void updateAdSettings(final MaxAdapterParameters parameters)
{
final Bundle serverParameters = parameters.getServerParameters();
if ( serverParameters.containsKey( "video_autoplay" ) )
{
final boolean videoAutoplay = serverParameters.getBoolean( "video_autoplay" );
AdSettings.setVideoAutoplay( videoAutoplay );
}
AdSettings.setMixedAudience( parameters.isAgeRestrictedUser() );
final String testDevicesString = serverParameters.getString( "test_device_ids", null );
if ( !TextUtils.isEmpty( testDevicesString ) )
{
final List<String> testDeviceList = Arrays.asList( testDevicesString.split( "," ) );
AdSettings.addTestDevices( testDeviceList );
}
// Update mediation service
AdSettings.setMediationService( getMediationIdentifier() );
}
private static MaxAdapterError toMaxError(final AdError adError)
{
final int facebookErrorCode = adError.getErrorCode();
final int maxErrorCode;
// Facebook's SDK sometimes creates new instances of their pre-defined enums, so we should extract the raw int, and not do pointer equality
if ( facebookErrorCode == AdError.NETWORK_ERROR.getErrorCode() ) // 1000
{
maxErrorCode = MaxAdapterError.ERROR_CODE_NO_CONNECTION; // -5207
}
else if ( facebookErrorCode == AdError.NO_FILL.getErrorCode() ) // 1001
{
maxErrorCode = MaxAdapterError.ERROR_CODE_NO_FILL; // 204
}
else if ( facebookErrorCode == AdError.LOAD_TOO_FREQUENTLY.getErrorCode() ) // 1002
{
maxErrorCode = MaxAdapterError.ERROR_CODE_INVALID_LOAD_STATE; // -5201
}
else if ( facebookErrorCode == AdError.SERVER_ERROR.getErrorCode() ) // 2000
{
maxErrorCode = MaxAdapterError.ERROR_CODE_SERVER_ERROR; // -5208
}
else if ( facebookErrorCode == AdError.INTERNAL_ERROR.getErrorCode() ) // 2001 - it's actually a timeout event...
{
maxErrorCode = MaxAdapterError.ERROR_CODE_TIMEOUT; // -5209
}
else
{
maxErrorCode = MaxAdapterError.ERROR_CODE_UNSPECIFIED; // -5200
}
return new MaxAdapterError( maxErrorCode, facebookErrorCode );
}
private String getMediationIdentifier()
{
return "APPLOVIN_" + AppLovinSdk.VERSION + ":" + getAdapterVersion();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment