Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Expand
titleALMobileFuseMediationAdapter.m
Code Block
languageobjective-c
//
//  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 != nil && [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 != nil && [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 != nil && [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