//
// ALMobileFuseMediationAdapter.m
// AppLovin MAX Mediation Adapter for MobileFuse
//
#import "ALMobileFuseMediationAdapter.h"
// Adapter version number is SDK_VERSION.ADAPTER_REVISION (e.g 1.0.0.000)
#define ADAPTER_REVISION (@"0") // Increment this if we need to release a patched adapter for a particular SDK version (and set to 0 for next new SDK version)
@interface ALMobileFuseMediationAdapterAdViewDelegate : NSObject<IMFAdCallbackReceiver>
@property (nonatomic, weak, readwrite) ALMobileFuseMediationAdapter *parentAdapter;
@property (nonatomic, strong, readwrite) id<MAAdViewAdapterDelegate> delegate;
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate;
@end
@interface ALMobileFuseMediationAdapterInterstitialAdDelegate : NSObject<IMFAdCallbackReceiver>
@property (nonatomic, weak, readwrite) ALMobileFuseMediationAdapter *parentAdapter;
@property (nonatomic, strong, readwrite) id<MAInterstitialAdapterDelegate> delegate;
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate;
@end
@interface ALMobileFuseMediationAdapterRewardedAdDelegate : NSObject<IMFAdCallbackReceiver>
@property (nonatomic, weak, readwrite) ALMobileFuseMediationAdapter *parentAdapter;
@property (nonatomic, strong, readwrite) id<MARewardedAdapterDelegate> delegate;
@property (nonatomic, readwrite) BOOL grantedReward;
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate;
@end
@interface ALMobileFuseMediationAdapter()
@property (nonatomic, strong, readwrite) MFInterstitialAd *interstitial;
@property (nonatomic, strong, readwrite) MFBannerAd *adView;
@property (nonatomic, strong, readwrite) MFRewardedAd *rewarded;
@property (nonatomic, strong, readwrite) ALMobileFuseMediationAdapterInterstitialAdDelegate *interstitialAdapterDelegate;
@property (nonatomic, strong, readwrite) ALMobileFuseMediationAdapterAdViewDelegate *adViewAdapterDelegate;
@property (nonatomic, strong, readwrite) ALMobileFuseMediationAdapterRewardedAdDelegate *rewardedAdapterDelegate;
@property (nonatomic, copy) void (^initializationCompletionHandler)(MAAdapterInitializationStatus, NSString * _Nullable);
@end
@implementation ALMobileFuseMediationAdapter
#pragma mark - MAAdapter Methods
- (NSString *)SDKVersion
{
return [MobileFuse version];
}
- (NSString *)adapterVersion
{
return [NSString stringWithFormat: @"%@.%@", [self SDKVersion], ADAPTER_REVISION];
}
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters completionHandler:(void (^)(MAAdapterInitializationStatus, NSString * _Nullable))completionHandler
{
// If the SDK is already initialized, just give a success callback straight away
if ( [MobileFuse isReady] )
{
completionHandler(MAAdapterInitializationStatusInitializedSuccess, nil);
return;
}
// If the app_id is not set or empty, we skip the SDK internal init as we are presuming to run as a bidding adapter only
if ( !parameters.serverParameters[@"app_id"] || [parameters.serverParameters[@"app_id"] length] == 0 )
{
[self log: @"No app key is set for initialization, running with bidding support only %@", self];
completionHandler(MAAdapterInitializationStatusInitializedSuccess, nil);
return;
}
// Parse our app ID, in MAX our publishers provide both numeric IDs separated by an underscore (<pubid>_<appid>)
NSArray *mobileFuseAppKeyParts = [parameters.serverParameters[@"app_id"] componentsSeparatedByString: @"_"];
if ( [mobileFuseAppKeyParts count] != 2 )
{
completionHandler(MAAdapterInitializationStatusInitializedFailure, @"Invalid app key supplied");
return;
}
NSString *mobileFusePublisherId = mobileFuseAppKeyParts[0];
NSString *mobileFuseAppId = mobileFuseAppKeyParts[1];
// Keep a reference to the completion handler so we can trigger it when our initialization delegate methods are invoked
self.initializationCompletionHandler = completionHandler;
// Now initialize the MobileFuse SDK
[MobileFuse initWithAppId: mobileFuseAppId withPublisherId: mobileFusePublisherId withDelegate: (id<IMFInitializationCallbackReceiver>)self];
completionHandler(MAAdapterInitializationStatusInitializing, nil);
}
- (void)destroy
{
if ( self.adView )
{
[self.adView destroy];
self.adView = nil;
}
if ( self.interstitial )
{
[self.interstitial destroy];
self.interstitial = nil;
}
if ( self.rewarded )
{
[self.rewarded destroy];
self.rewarded = nil;
}
self.interstitialAdapterDelegate = nil;
self.adViewAdapterDelegate = nil;
self.rewardedAdapterDelegate = nil;
}
#pragma mark - MASignalProvider Methods
- (void)collectSignalWithParameters:(id<MASignalCollectionParameters>)parameters andNotify:(id<MASignalCollectionDelegate>)delegate
{
[self log: @"Collecting signal..."];
MFBiddingTokenRequest* request = [[MFBiddingTokenRequest alloc] init];
request.privacyPreferences = [self getPrivacyPreferences: parameters];
request.isTestMode = parameters.isTesting;
NSString* token = [MFBiddingTokenProvider getTokenWithRequest: request];
[delegate didCollectSignal: token];
}
#pragma mark - MAInterstitialAdapter Methods
- (void)loadInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *placementId = parameters.thirdPartyAdPlacementIdentifier;
NSString *bidResponse = parameters.bidResponse;
BOOL isBiddingAd = [bidResponse al_isValidString];
[self log: @"Loading %@interstitial ad for placement \"%@\"...", isBiddingAd ? @"bidding " : @"", placementId];
if ( self.interstitial )
{
[self.interstitial destroy];
self.interstitial = nil;
}
self.interstitialAdapterDelegate = [[ALMobileFuseMediationAdapterInterstitialAdDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.interstitial = [[MFInterstitialAd alloc] initWithPlacementId: placementId];
[self.interstitial registerAdCallbackReceiver: self.interstitialAdapterDelegate];
[[ALUtils topViewControllerFromKeyWindow].view addSubview: self.interstitial];
if ( isBiddingAd )
{
[self.interstitial loadAdWithBiddingResponseToken: bidResponse];
}
else
{
[self.interstitial loadAd];
}
});
}
- (void)showInterstitialAdForParameters:(nonnull id<MAAdapterResponseParameters>)parameters andNotify:(nonnull id<MAInterstitialAdapterDelegate>)delegate
{
dispatch_async(dispatch_get_main_queue(), ^{
[self log: @"Showing interstitial ad..."];
if ( !self.interstitial )
{
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.invalidLoadState];
return;
}
if ( [self.interstitial isExpired] )
{
[self log: @"Failed to show interstitial ad because the ad has expired. Please try to ensure that you load ads near to the time that you intend to show them."];
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.adExpiredError];
return;
}
if ( ![self.interstitial isLoaded] )
{
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.adNotReady];
return;
}
[self.interstitial showAd];
});
}
#pragma mark - MARewardedAdapter Methods
- (void)loadRewardedAdForParameters:(nonnull id<MAAdapterResponseParameters>)parameters andNotify:(nonnull id<MARewardedAdapterDelegate>)delegate
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *placementId = parameters.thirdPartyAdPlacementIdentifier;
NSString *bidResponse = parameters.bidResponse;
BOOL isBiddingAd = [bidResponse al_isValidString];
[self log: @"Loading %@rewarded ad for placement \"%@\"...", isBiddingAd ? @"bidding " : @"", placementId];
if ( self.rewarded )
{
[self.rewarded destroy];
self.rewarded = nil;
}
self.rewardedAdapterDelegate = [[ALMobileFuseMediationAdapterRewardedAdDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.rewarded = [[MFRewardedAd alloc] initWithPlacementId: placementId];
[self.rewarded registerAdCallbackReceiver: self.rewardedAdapterDelegate];
[[ALUtils topViewControllerFromKeyWindow].view addSubview: self.rewarded];
if ( isBiddingAd )
{
[self.rewarded loadAdWithBiddingResponseToken: bidResponse];
}
else
{
[self.rewarded loadAd];
}
});
}
- (void)showRewardedAdForParameters:(nonnull id<MAAdapterResponseParameters>)parameters andNotify:(nonnull id<MARewardedAdapterDelegate>)delegate
{
dispatch_async(dispatch_get_main_queue(), ^{
[self log: @"Showing rewarded ad..."];
if ( !self.rewarded )
{
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.invalidLoadState];
return;
}
if ( [self.rewarded isExpired] )
{
[self log: @"Failed to show rewarded ad because the ad has expired. Please try to ensure that you load ads near to the time that you intend to show them."];
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.adExpiredError];
return;
}
if ( ![self.rewarded isLoaded] )
{
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.adNotReady];
return;
}
[self.rewarded showAd];
});
}
#pragma mark - MAAdViewAdapter Methods
- (void)loadAdViewAdForParameters:(id<MAAdapterResponseParameters>)parameters
adFormat:(MAAdFormat *)adFormat
andNotify:(id<MAAdViewAdapterDelegate>)delegate
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *placementId = parameters.thirdPartyAdPlacementIdentifier;
NSString *bidResponse = parameters.bidResponse;
BOOL isBiddingAd = [bidResponse al_isValidString];
[self log: @"Loading %@%@ ad for placement \"%@\"...", isBiddingAd ? @"bidding " : @"", adFormat.label, placementId];
if ( self.adView )
{
[self.adView destroy];
self.adView = nil;
}
// Get ad size from the ad format, but allow the user to force it using a custom parameter
NSDictionary *customData = [[parameters serverParameters] al_dictionaryForKey: @"custom_parameters"];
MFBannerAdSize bannerSize = [self getBannerAdSizeFromAdFormat: adFormat];
if ( customData[@"forcedSize"] )
{
// If the placement has a custom "forcedSize" parameter in its JSON, override with that
[self log: @"Override ad size with size from custom data: %@", customData[@"forcedSize"]];
bannerSize = [self getBannerAdSizeFromString: customData[@"forcedSize"]];
}
self.adViewAdapterDelegate = [[ALMobileFuseMediationAdapterAdViewDelegate alloc] initWithParentAdapter: self andNotify: delegate];
self.adView = [[MFBannerAd alloc] initWithPlacementId: placementId withSize: bannerSize];
[self.adView registerAdCallbackReceiver: self.adViewAdapterDelegate];
// If the placement has a custom "initiallyMuted" parameter, use that to override initial mute state of banners
if ( customData[@"initiallyMuted"] )
{
[self log: @"Override ad mute state with value from custom data: %@", [customData[@"initiallyMuted"] boolValue] ? @"YES" : @"NO"];
[self.adView setMuted: [customData[@"initiallyMuted"] boolValue]];
}
if ( isBiddingAd )
{
[self.adView loadAdWithBiddingResponseToken: bidResponse];
}
else
{
[self.adView loadAd];
}
});
}
- (MFBannerAdSize)getBannerAdSizeFromAdFormat:(MAAdFormat *)adFormat
{
if ( adFormat == MAAdFormat.banner)
{
// Represents a 320x50 banner advertisement
[self log: @"%@ Ad size is 320x50", @(__PRETTY_FUNCTION__)];
return MOBILEFUSE_BANNER_SIZE_320x50;
}
else if ( adFormat == MAAdFormat.leader )
{
// Represents a 728x90 leaderboard advertisement (for tablets)
[self log: @"%@ Ad size is 728x90", @(__PRETTY_FUNCTION__)];
return MOBILEFUSE_BANNER_SIZE_728x90;
}
else if ( adFormat == MAAdFormat.mrec )
{
// Represents a 300x250 rectangular advertisement
[self log: @"%@ Ad size is 300x250", @(__PRETTY_FUNCTION__)];
return MOBILEFUSE_BANNER_SIZE_300x250;
}
else
{
[self log: @"getBannerAdSizeFromAdFormat warning: Unsupported ad format specified, using default banner size"];
return MOBILEFUSE_BANNER_SIZE_DEFAULT;
}
}
- (MFBannerAdSize)getBannerAdSizeFromString:(NSString*)size
{
NSDictionary *sizeMap = @{
@"320x50": @(MOBILEFUSE_BANNER_SIZE_320x50),
@"300x50": @(MOBILEFUSE_BANNER_SIZE_300x50),
@"300x250": @(MOBILEFUSE_BANNER_SIZE_300x250),
@"728x90": @(MOBILEFUSE_BANNER_SIZE_728x90)
};
if ( sizeMap[size] )
{
return [sizeMap[size] integerValue];
}
return MOBILEFUSE_BANNER_SIZE_DEFAULT;
}
#pragma mark - Privacy
- (MobileFusePrivacyPreferences*)getPrivacyPreferences:(id<MAAdapterParameters>) parameters
{
MobileFusePrivacyPreferences *prefs = [[MobileFusePrivacyPreferences alloc] init];
if ( ALSdk.versionCode >= 61100 )
{
NSNumber *isDoNotSell = [self privacySettingForSelector: @selector(isDoNotSell) fromParameters: parameters];
if ( isDoNotSell )
{
[prefs setUsPrivacyConsentString: isDoNotSell.boolValue ? @"1YY-" : @"1YN-"];
}
else
{
[prefs setUsPrivacyConsentString: @"1---"];
}
}
NSNumber *isAgeRestrictedUser = [self privacySettingForSelector: @selector(isAgeRestrictedUser) fromParameters: parameters];
if ( isAgeRestrictedUser )
{
[prefs setSubjectToCoppa: isAgeRestrictedUser.boolValue];
}
if ( ALSdk.versionCode >= 11040299 )
{
if ( parameters.consentString )
{
[prefs setIabConsentString: parameters.consentString];
}
}
return prefs;
}
- (nullable NSNumber *)privacySettingForSelector:(SEL)selector fromParameters:(id<MAAdapterParameters>)parameters
{
// Use reflection because compiled adapters have trouble fetching `BOOL` from old SDKs and `NSNumber` from new SDKs (above 6.14.0)
NSMethodSignature *signature = [[parameters class] instanceMethodSignatureForSelector: selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setSelector: selector];
[invocation setTarget: parameters];
[invocation invoke];
// Privacy parameters return nullable NSNumber on newer SDKs
if ( ALSdk.versionCode >= 6140000 )
{
NSNumber *__unsafe_unretained value;
[invocation getReturnValue: &value];
return value;
}
// Privacy parameters return BOOL on older SDKs
else
{
BOOL rawValue;
[invocation getReturnValue: &rawValue];
return @(rawValue);
}
}
#pragma mark - Initialization Delegates
// IMFInitializationCallbackReceiver delegate implementation
- (void)onInitSuccess:(NSString *)appId withPublisherId:(NSString *)publisherId
{
[self log: @"Initialization succeeded"];
self.initializationCompletionHandler(MAAdapterInitializationStatusInitializedSuccess, nil);
}
- (void)onInitError:(NSString *)message forAppId:(NSString *)appId withPublisherId:(NSString *)publisherId
{
[self log: @"Error during initialization with message: %@", message];
self.initializationCompletionHandler(MAAdapterInitializationStatusInitializedFailure, nil);
}
@end
#pragma mark - Banner Delegate
@implementation ALMobileFuseMediationAdapterAdViewDelegate
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
// IMFAdCallbackReceiver delegate implementation
- (void)onAdLoaded:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didLoadAdForAdView: ad];
// Show the banner ad immediately
dispatch_async(dispatch_get_main_queue(), ^{
[(MFBannerAd *)ad showAdWithViewController: [ALUtils topViewControllerFromKeyWindow]];
});
}
- (void)onAdNotFilled:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didFailToLoadAdViewAdWithError: MAAdapterError.noFill];
}
- (void)onAdClosed:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didHideAdViewAd];
}
- (void)onAdRendered:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didDisplayAdViewAd];
}
- (void)onAdExpanded:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didExpandAdViewAd];
}
- (void)onAdCollapsed:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didCollapseAdViewAd];
}
- (void)onAdClicked:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
[self.delegate didClickAdViewAd];
}
- (void)onAdExpired:(MFAd *)ad
{
[self.parentAdapter log: @"AdView %@", @(__PRETTY_FUNCTION__)];
}
- (void)onAdError:(MFAd *)ad withMessage:(NSString *)message
{
[self.parentAdapter log: @"AdView %@ with message: %@", @(__PRETTY_FUNCTION__), message];
[self.delegate didFailToLoadAdViewAdWithError: MAAdapterError.internalError];
}
@end
#pragma mark - Interstitial Delegate
@implementation ALMobileFuseMediationAdapterInterstitialAdDelegate
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
// IMFAdCallbackReceiver delegate implementation
- (void)onAdLoaded:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@ with placement ID ", @(__PRETTY_FUNCTION__), ad.placementId];
[self.delegate didLoadInterstitialAd];
}
- (void)onAdNotFilled:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@ with placement ID ", @(__PRETTY_FUNCTION__), ad.placementId];
[self.delegate didFailToLoadInterstitialAdWithError: MAAdapterError.noFill];
}
- (void)onAdClosed:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@", @(__PRETTY_FUNCTION__)];
[self.delegate didHideInterstitialAd];
}
- (void)onAdRendered:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@", @(__PRETTY_FUNCTION__)];
[self.delegate didDisplayInterstitialAd];
}
- (void)onAdClicked:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@", @(__PRETTY_FUNCTION__)];
[self.delegate didClickInterstitialAd];
}
- (void)onAdExpired:(MFAd *)ad
{
[self.parentAdapter log: @"Interstitial %@", @(__PRETTY_FUNCTION__)];
}
- (void)onAdError:(MFAd *)ad withMessage:(NSString *)message
{
[self.parentAdapter log: @"Interstitial %@ with message: %@", @(__PRETTY_FUNCTION__), message];
[self.delegate didFailToLoadInterstitialAdWithError: MAAdapterError.internalError];
}
@end
#pragma mark - Rewarded Delegate
@implementation ALMobileFuseMediationAdapterRewardedAdDelegate
@synthesize grantedReward;
- (instancetype)initWithParentAdapter:(ALMobileFuseMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate
{
self = [super init];
if ( self )
{
self.parentAdapter = parentAdapter;
self.delegate = delegate;
}
return self;
}
// IMFAdCallbackReceiver delegate implementation
- (void)onAdLoaded:(MFAd *)ad
{
[self.parentAdapter log: @"Rewarded %@ with placement ID ", @(__PRETTY_FUNCTION__), ad.placementId];
[self.delegate didLoadRewardedAd];
}
- (void)onAdNotFilled:(MFAd *)ad {
[self.parentAdapter log: @"Rewarded %@ with placement ID ", @(__PRETTY_FUNCTION__), ad.placementId];
[self.delegate didFailToLoadRewardedAdWithError: MAAdapterError.noFill];
}
- (void)onAdRendered:(MFAd *)ad {
[self.parentAdapter log: @"Rewarded %@", @(__PRETTY_FUNCTION__)];
[self.delegate didDisplayRewardedAd];
}
- (void)onUserEarnedReward:(MFAd *)ad
{
[self.delegate didCompleteRewardedAdVideo];
self.grantedReward = YES;
}
- (void)onAdClosed:(MFAd *)ad
{
[self.parentAdapter log: @"Rewarded %@", @(__PRETTY_FUNCTION__)];
if ( self.grantedReward )
{
MAReward *reward = self.parentAdapter.reward;
[self.parentAdapter log: @"Rewarded ad user with reward: %@", reward];
[self.delegate didRewardUserWithReward: reward];
self.grantedReward = NO;
}
[self.delegate didHideRewardedAd];
}
- (void)onAdClicked:(MFAd *)ad
{
[self.parentAdapter log: @"Rewarded %@", @(__PRETTY_FUNCTION__)];
[self.delegate didClickRewardedAd];
}
- (void)onAdExpired:(MFAd *)ad
{
[self.parentAdapter log: @"Rewarded %@", @(__PRETTY_FUNCTION__)];
}
- (void)onAdError:(MFAd *)ad withMessage:(NSString *)message
{
[self.parentAdapter log: @"Rewarded %@ with message: %@", @(__PRETTY_FUNCTION__), message];
[self.delegate didFailToLoadRewardedAdWithError: MAAdapterError.internalError];
}
@end